Git Product home page Git Product logo

fireblocks-sdk-py's People

Contributors

amimagid avatar amitshe avatar asafs932 avatar aviba avatar barakfireblocks avatar dpetrovic-fb avatar grishadev avatar idanya avatar itamar17 avatar knaperek avatar lazars14-f avatar michael-fireblocks avatar nitaiaharoni1 avatar noamrodrik avatar or109 avatar pbereng avatar shirgerasi10 avatar slavasereb avatar snirs avatar tomervil avatar vsiskin avatar yahavamar avatar yanivmo avatar yanlevi avatar yarinvak avatar yinon-hever avatar yoavbz avatar yonimo avatar yuval-fireblocks avatar zoharsf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fireblocks-sdk-py's Issues

`get_external_wallet()` is not retrieving the customerRefId

Describe the bug
The method get_external_wallet() is not retrieving the customerRefId property, even though this property is set for a wallet (not null).

To Reproduce
Steps to reproduce the behavior:

  1. Write the following code:
from fireblocks_sdk import FireblocksSDK
import json

with open('/path/to/secret.key', 'r') as file: # Replace '/path/to/secret.key' with a valid path
    fireblocks = FireblocksSDK(
        private_key=file.read(),
        api_key='my_api_key', # Replace 'my_api_key' with a valid API Key
        api_base_url='https://api.fireblocks.io'
    )
    my_external_wallet_id = 'aac59cad-6bc7-931c-bddf-cbc633b455ea' # A valid walletId
    ew = fireblocks.get_external_wallet(my_external_wallet_id)
    print("##### Retrieving one external wallet:")
    print(json.dumps(ew, indent=2))

    external_wallets = fireblocks.get_external_wallets()
    print("##### Retrieving all the external wallets and getting only the same retrieved one above:")
    for wallet in external_wallets:
        if wallet['id'] == ew['id']:
            print(json.dumps(wallet, indent=2))
            break
  1. Run the code.

Expected behavior
It should display two dictionaries printed out, according to API specifications (https://docs.fireblocks.com/api/?python#externalwallet) and they should be exactly the same object.

Actual behavior
The obtained object by using get_external_wallet() (single) doesn't have the property customerRefId, but the obtained object by using get_external_wallets() (multiple) does have that property:

##### Retrieving one external wallet:
{
  "id": "aac59cad-6bc7-931c-bddf-cbc633b455ea",
  "name": "Ricardo_ETH-ETH_TEST-EV3K05V9CDX2",
  "assets": [
    {
      "id": "ETH_TEST",
      "status": "APPROVED",
      "address": "0x26B13e3927022988cd7D805D5bc85cD9Fc06208b",
      "tag": ""
    }
  ]
}
##### Retrieving all the external wallets and getting only the same retrieved one above:
{
  "id": "aac59cad-6bc7-931c-bddf-cbc633b455ea",
  "name": "Ricardo_ETH-ETH_TEST-EV3K05V9CDX2",
  "customerRefId": "Ricardo_ETH-ETH_TEST-EV3K05V9CDX2",
  "assets": [
    {
      "id": "ETH_TEST",
      "status": "APPROVED",
      "address": "0x26B13e3927022988cd7D805D5bc85cD9Fc06208b",
      "tag": ""
    }
  ]
}

Versions:

  • Python Version: 3.9
  • fireblocks-sdk version: 1.16.0

FireblocksApiException does not contain the error code

When getting a 4xx or 5xx response, the sdk raises FireblocksApiException, with the following args:

('Got an error from fireblocks server: {"message":"The external tx id that was provided in the request, already exists","code":1438}',)

Error code is presented inside the error message, which is tricky to parse and extract. Would be great to have the error_code integer property in FireblocksApiException.

JSONDecodeError on 404

Describe the bug

In some cases, when receiving a 404 from the server, the response is HTML and not JSON. In this case, response.json() will throw a JSONDecodeError. This propagates to the caller instead of being correctly handled and logged.

To Reproduce
Steps to reproduce the behavior:

  1. Use the SDK to lookup a non-existent transaction
  2. Observe the JSONDecodeError on the 404

Expected behavior
Non-JSON response codes should correctly be handled and not result in an exception.

Versions (please complete the following information):

  • Python Version: 3.9
  • fireblocks-sdk version: 1.11.1

random `500` internal error and `RemoteDisconnected`

Describe the bug
I am constantly getting 500 internal error responses for get_vault_assets_balance. Other times I am also receiving:

requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

To Reproduce
Please see below

Expected behavior
I am trying to make a snapshot of our Fireblocks account for backup and also accounting purposes. I am trying to iterate over each vault and its assets and gathering balances for each.

Versions (please complete the following information):

  • Python Version: 3.12.1
  • fireblocks-sdk version: fireblocks_sdk==2.5.1

Additional context
Here is an excerpt of my related code snippets and logs:

def catch_and_retry_fireblocks_exception(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        max_retries = 3
        retries = 0
        while retries < max_retries:
            try:
                return func(self, *args, **kwargs)
            except FireblocksApiException as e:
                self.logger.error(
                    f"[Fireblocks] Error occurred for function {func.__name__}: {e}"
                )
                self.logger.error(f"[Fireblocks] Arguments: {args}, {kwargs}")
                # Handle the exception as needed
                retries += 1
                if retries < max_retries:
                    # Wait for 1 second before retrying
                    time.sleep(0.1)
                    self.logger.info(f"[Fireblocks] Retrying {func.__name__}...")
                else:
                    self.logger.warning(
                        f"[Fireblocks] Maximum retries reached. Skipping {func.__name__}."
                    )

    return wrapper


class FireblocksSDKWrapper:

    def __init__(self, api_key: str, private_key: str):
        self.fireblocks = FireblocksSDK(api_key=api_key, private_key=private_key)
        self.logger = Logger(name=__name__, level="INFO").get_logger()

    @catch_and_retry_fireblocks_exception
    def get_vault_accounts_paginated(self):
        next_page = True
        paged_filter = PagedVaultAccountsRequestFilters()
        accounts = []
        while next_page:
            vaults = self.fireblocks.get_vault_accounts_with_page_info(paged_filter)
            accounts = vaults["accounts"]
            accounts.extend(accounts)

            # Paginate through accounts
            if vaults.get("paging").get("after") is None:
                next_page = False
            else:
                paged_filter.after = vaults.get("paging").get("after")

        return accounts

    @catch_and_retry_fireblocks_exception
    def get_internal_wallets(self):
        return self.fireblocks.get_internal_wallets()

    @catch_and_retry_fireblocks_exception
    def get_deposit_addresses(self, vault_account_id: typing.Any, asset_id: typing.Any):
        return self.fireblocks.get_deposit_addresses(vault_account_id, asset_id)

    @catch_and_retry_fireblocks_exception
    def get_vault_assets_balance(
        self,
        account_name_prefix: typing.Any | None = None,
        account_name_suffix: typing.Any | None = None,
    ):
        return self.fireblocks.get_vault_assets_balance(
            account_name_prefix,
            account_name_suffix,
        )


@dataclasses.dataclass
class FireblocksAccountingLoader(ExchangeLoaderBase):
    fireblocks: typing.Optional[FireblocksSDKWrapper] = dataclasses.field(init=True)
    vault_accounts: typing.List[dict] = dataclasses.field(init=False)
    internal_wallets: typing.List[dict] = dataclasses.field(init=False)
    accounts: typing.List[dict] = dataclasses.field(init=False)
    logger: logging.Logger = dataclasses.field(
        default_factory=lambda: logging.Logger(__name__)
    )

    def __post_init__(self):
        self.vault_accounts = self.fireblocks.get_vault_accounts_paginated()
        self.internal_wallets = self.fireblocks.get_internal_wallets()
        self.accounts = self.vault_accounts  # + self.internal_wallets

    def fetch_accounts(self, utc_timestamp: str) -> pd.DataFrame:
        df = pd.DataFrame(self.accounts)

        # explode the assets column
        df = df.explode("assets")

        # Cast nan assets to {}
        df.assets = df.assets.fillna({i: {} for i in df.index})

        # create new columns for each key in the dictionaries
        df["asset_id"] = df["assets"].apply(lambda x: x["id"] if "id" in x else None)
        df["asset_total"] = df["assets"].apply(
            lambda x: x["total"] if "total" in x else None
        )
        df["asset_balance"] = df["assets"].apply(
            lambda x: x["balance"] if "balance" in x else None
        )
        df["account_type"] = df["name"].apply(
            lambda x: (
                "internal_wallet" if x == "Gas Station Wallet" else "vault_account"
            )
        )

        # Cast id to int
        df["id"] = df["id"].astype(int)

        # Remove "Network Deposits" vault accounts
        df = df[df.name != "Network Deposits"]

        # Add the timestamp to the dataframe
        df["timestamp"] = utc_timestamp
        # drop the original assets column
        df = df.drop("assets", axis=1)
        # Reset index
        df = df.reset_index(drop=True)

        # Fetch asset_id to a dict

        # Get the vault account asset information
        for row, col in df.iterrows():
            # Skip if the account is an internal wallet
            if col.account_type == "internal_wallet":
                # Get an index of the internal wallet
                internal_wallet_index = [
                    i for i, x in enumerate(self.internal_wallets) if x["id"] == col.id
                ][0]
                asset_info = self.internal_wallets[internal_wallet_index]["assets"]
            elif col.account_type == "vault_account" and col.asset_id is not None:
                asset_info = self.fireblocks.get_deposit_addresses(
                    vault_account_id=col.id,
                    asset_id=col.asset_id,
                )
            else:
                asset_info = []
            # Restructure the asset_info into a dictionary
            asset_dict = {}
            for asset in asset_info:
                asset_dict[asset["address"]] = {
                    "asset_id": asset["assetId"] if "assetId" in asset else None,
                    "address": asset["address"] if "address" in asset else None,
                    "tag": asset["tag"] if "tag" in asset else None,
                    "description": (
                        asset["description"] if "description" in asset else None
                    ),
                    "type": asset["type"] if "type" in asset else None,
                    "addressFormat": (
                        asset["addressFormat"] if "addressFormat" in asset else None
                    ),
                    "legacyAddress": (
                        asset["legacyAddress"] if "legacyAddress" in asset else None
                    ),
                    "bip44AddressIndex": (
                        asset["bip44AddressIndex"]
                        if "bip44AddressIndex" in asset
                        else None
                    ),
                }
            # Add asset_info as a column to the dataframe being iterated over
            df.loc[row, "addresses"] = [[asset_dict]]

        # Get additional balance information
        df_assets = pd.DataFrame()
        for vault_name in df.name.unique():
            # Skip if the account is an internal wallet
            if vault_name == "Gas Station Wallet":
                continue
            asset_balances = pd.DataFrame(
                self.fireblocks.get_vault_assets_balance(vault_name)
            )
            asset_balances = asset_balances.rename(columns={"id": "asset_id"})
            asset_balances["name"] = vault_name
            df_assets = pd.concat([df_assets, asset_balances], axis=0)

        # Merge the asset balances with the dataframe
        df_merged = pd.merge(df, df_assets, on=["name", "asset_id"])

        # Calculate prices
        asset_ids = df_merged.asset_id.unique()
        asset_prices = {}
        for asset_id in asset_ids:
            try:
                asset_id_split = asset_id.split("_")[0]
                asset_prices[asset_id] = self.calculate_usd_price(
                    asset=asset_id_split,
                    exchange=ccxt.bitmart(),  # Using bitmart since only they have RND
                )
            except Exception as e:  # pylint: disable=broad-except
                self.logger.info(
                    "[Fireblocks] Could not fetch price for %s (error: %s)",
                    asset_id,
                    e,
                )
                continue

        for row, col in df_merged.iterrows():
            try:
                asset_price = asset_prices[col.asset_id]
                df_merged.loc[row, "usd_price"] = asset_prices[col.asset_id]
                df_merged.loc[row, "usd_value"] = (
                    col.asset_total * asset_price if asset_price is not None else None
                )
            except Exception as e:  # pylint: disable=broad-except
                self.logger.info(
                    "[Fireblocks] Could not set price for %s: %s)", col.asset_id, e
                )
                continue

        return df_merged

    def to_dataframe(self, utc_timestamp: pd.Timestamp = None):
        return self.fetch_accounts(
            utc_timestamp=(
                pd.Timestamp.utcnow() if utc_timestamp is None else utc_timestamp
            )
        )
2024-03-07 10:56:19,315 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 10:56:19,426 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:08:53,224 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:08:53,327 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:11:35,880 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:11:35,984 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:14:23,272 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:14:23,378 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:17:10,825 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:17:10,932 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:19:52,531 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:19:52,640 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:22:33,295 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:22:33,401 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:25:17,782 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:25:17,890 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
2024-03-07 11:28:00,107 - rand_treasury_monitoring.utils.fireblocks - ERROR - [Fireblocks] Error occurred for function get_vault_assets_balance: Got an error from fireblocks server: 500

2024-03-07 11:28:00,211 - rand_treasury_monitoring.utils.fireblocks - INFO - [Fireblocks] Retrying get_vault_assets_balance...
Exception has occurred: ConnectionError       (note: full exception trace is shown but execution is paused at: <module>)
('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
http.client.RemoteDisconnected: Remote end closed connection without response

During handling of the above exception, another exception occurred:

urllib3.exceptions.ProtocolError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

During handling of the above exception, another exception occurred:

  File "/Users/adr/Dev/Rand-Network/treasury-balance-service/rand_treasury_monitoring/utils/fireblocks.py", line 74, in get_vault_assets_balance
    return self.fireblocks.get_vault_assets_balance(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adr/Dev/Rand-Network/treasury-balance-service/rand_treasury_monitoring/utils/fireblocks.py", line 17, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adr/Dev/Rand-Network/treasury-balance-service/rand_treasury_monitoring/data/loaders.py", line 316, in fetch_accounts
    self.fireblocks.get_vault_assets_balance(vault_name)
  File "/Users/adr/Dev/Rand-Network/treasury-balance-service/rand_treasury_monitoring/data/loaders.py", line 357, in to_dataframe
    return self.fetch_accounts(
           ^^^^^^^^^^^^^^^^^^^^
  File "/var/folders/w6/dh79cy2x0sjdkb2y4vrs8kq00000gn/T/ipykernel_18952/3364672574.py", line 1, in <module> (Current frame)
    loader.to_dataframe()
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

get_users() crashes

Describe the bug
get_users() crashes with

FireblocksApiException: Got an error from fireblocks server: { "message": "ok", "code": 0}

To Reproduce
Steps to reproduce the behavior:

  1. initialize the SDK
  2. call .get_users()

Expected behavior
Should return the list of users.

Versions (please complete the following information):

  • Python Version: [3.8, 3.9]
  • fireblocks-sdk version: [1.23.0, 2.2.1]

ModuleNotFoundError: No module named 'fireblocks_sdk'

Hi,

I am trying to make a basic request to Fireblocks API using python but getting stopped out at the from/import line. It can’t find the module even though it is shown in my list of python packages.

I’ve copied the line directly from the Github. Any ideas what is going on?

If its just list these 4 lines, I will still get the module error (but if I comment out the 4th line the file executes smoothly)

image

Ive also confirmed the fireblocks-sdk is in correct path location as other modules

image

Bug introduced in 1.6.3 with `treat_as_gross_amount`

Release 1.6.3 in 7e3d66c you've removed treat_as_gross_amount from the arguments list, but not from the body preparation, so create_transaction does not work:

Traceback (most recent call last):
  File "/xxx/enricher.py", line 1147, in post
    tx_result = self.fireblocks.create_transaction(**kwargs)
  File "/xxx/venv/lib/python3.9/site-packages/fireblocks_sdk/sdk.py", line 720, in create_transaction
    if treat_as_gross_amount:
NameError: name 'treat_as_gross_amount' is not defined

Python types for Webhooks events

It'd be great if we had a method for parsing the JSON messages sent via Webhooks into Python objects (ideally MyPy-annotated classes). I think this would make it easier to program against the Webhooks API because devs wouldn't have to refer to the documentation for type information--it'd be right there in the code.

Allow users to provide a requests.Session to FireblocksSDK

Is your feature request related to a problem? Please describe.
I'm frustrated that I can't add retries to the FireblocksSDK like those described here.

Describe the solution you'd like
I'd like a session: requests.Session argument to be added to the FireblocksSDK constructor. Then, all requests would be routed through it instead of the global requests package.

Additional context
Allowing users to provide their own session would give a lot more flexibility in addition to retries, like adding logging.

error when running pip install fireblocks-sdk

Describe the bug
The documentation should be more precise in install requirments. pip install fireblocks-sdk is not enough.

To Reproduce

  1. Clean python venv
  2. pip install fireblocks-sdk gets error
  3. Scroll down to '....'
  4. See error:
    Traceback (most recent call last): File "/home/michael/PycharmProjects/pythonProject/venv/bin/pip3", line 5, in <module> from pip._internal.cli.main import main File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/cli/main.py", line 9, in <module> from pip._internal.cli.autocompletion import autocomplete File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/cli/autocompletion.py", line 10, in <module> from pip._internal.cli.main_parser import create_main_parser File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py", line 9, in <module> from pip._internal.build_env import get_runnable_pip File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/build_env.py", line 20, in <module> from pip._internal.cli.spinners import open_spinner File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/cli/spinners.py", line 9, in <module> from pip._internal.utils.logging import get_indentation File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/utils/logging.py", line 29, in <module> from pip._internal.utils.misc import ensure_dir File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/utils/misc.py", line 42, in <module> from pip._internal.locations import get_major_minor_version File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/locations/__init__.py", line 67, in <module> from . import _distutils File "/home/michael/PycharmProjects/pythonProject/venv/lib/python3.8/site-packages/pip/_internal/locations/_distutils.py", line 20, in <module> from distutils.cmd import Command as DistutilsCommand ModuleNotFoundError: No module named 'distutils.cmd'

Expected behavior
pip install fireblocks-sdk should work

Versions (please complete the following information):

  • Python Version: 3.8
  • fireblocks-sdk version: latest

does "Idempotency-Key" header work?

Describe the bug
In method _post_request() , idempotency_key parameter doesnt work
Maybe it's simply bug on (sdk.py, line:1595)

To Reproduce
call method(e.g. create_transaction) twice with same idempotency_key parameter
(it always makes two transactions)

Expected behavior
keep idempotency
(when set idempotency_key parameter, add Idempotency-Key header)

Versions (please complete the following information):

  • Python Version: 3.9
  • fireblocks-sdk version: 1.18.1

set up a `ping` endpoint

Is your feature request related to a problem? Please describe.
Not so much a problem, but for a service readiness probe I am calling get_supported_assets to check the FireblocksSDK is ready and working. It would be nice to call a ping endpoint just to confirm everything is ready as the SDK doesn't check the correctness are instantiation time.

Describe the solution you'd like

just a simple method like

def ping(self):
    """Pings Fireblocks API to confirm private_key and api_key are working correctly"""

    return self._get_request("/v1/ping")

>>> fireblocks = FireblocksSDK('a', 'b')
>>> fireblocks.ping()
raise ValueError("Could not deserialize key data....")

>>> fireblocks = FireblocksSDK(os.getenv('FIREBLOCKS_PRIVATE_KEY'), os.getenv('FIREBLOCKS_API_KEY'))
>>> fireblocks.ping()
'pong'

`get_exchange_account_asset()` returns the wrong asset

The bug
When I make a call to this endpoint, the API returns the wrong asset despite this asset being present in the vault account.

To Reproduce

DEBUG:urllib3.connectionpool:https://api.fireblocks.io:443 "GET /v1/exchange_accounts/vault_acct_id/BTC HTTP/1.1" 200 88
{'id': 'ETH', 'balance': '0.2', 'total': '0.2', 'available': '0.2', 'lockedAmount': '0.00000000'}

Versions (please complete the following information):

  • Python Version: 3.6.8
  • fireblocks-sdk version: 1.12.2

Literal type `dict[str, FieldMetadata]` breaks on Python <= 3.8

Describe the bug
The ContractUploadRequest class uses a literal dict to type hints the input_fields_metadata parameter. Literal type hints are supported from Python 3.9 onward.

To Reproduce
Steps to reproduce the behavior:

  1. Setup Python 3.8 or lower environment
  2. import fireblocks_sdk
  3. See an error as
input_fields_metadata: Optional[dict[str, FieldMetadata]] = None,
TypeError: 'type' object is not subscriptable

Expected behavior
Able to import fireblocks_sdk on Python >= 3.6

Versions (please complete the following information):

  • Python Version: 3.8.8
  • fireblocks-sdk version: v2.2.1

Additional context
The bug was introduced in PR #146

Support per-request timeout

Is your feature request related to a problem? Please describe.
I'm frustrated that I can't specify different timeouts for different requests because there is just a single timeout variable in the FireblocksSDK object.

Describe the solution you'd like
An optional timeout=None parameter added to all the methods in FireblocksSDK.

Error "maxGasPrice must be string" on set_gas_station_configuration

In the function set_gas_station_configuration parameter maxGasPrice is optional, however it is always added to body

        body = {
            "gasThreshold": gas_threshold,
            "gasCap": gas_cap,
            "maxGasPrice": max_gas_price,
        }

"maxGasPrice": max_gas_price,

Calling function without max_gas_price is rejected with error "maxGasPrice must be string".

This change of code resolved issue for me:

        body = {
            "gasThreshold": gas_threshold,
            "gasCap": gas_cap #,
#optional            "maxGasPrice": max_gas_price,
        }
        if max_gas_price:
           body["maxGasPrice"]=max_gas_price

Denis

Keyword arg for `max_gas_price`

Is your feature request related to a problem? Please describe.
Sorry me again. This is a tiny issue but I am a pedantic person.

In the method:

 def set_gas_station_configuration(self, gas_threshold, gas_cap, max_gas_price):
        """Set configuration of the Gas Station account

        Args:
            gasThreshold (str)
            gasCap (str)
            maxGasPrice (str, optional)
        """

        url = f"/v1/gas_station/configuration"

        body = {
            "gasThreshold": gas_threshold,
            "gasCap": gas_cap,
            "maxGasPrice": max_gas_price
        }

        return self._put_request(url, body)

It would be good if max_gas_price is optional, for it to be a keyword arg equal to None

Describe the solution you'd like
def set_gas_station_configuration(self, gas_threshold, gas_cap, max_gas_price=None):

Additional context

  1. Do you accept clients to open PRs on your code? I would have been happy to add this in myself
  2. Might be good to make all params optional so people can just change one element of the config

AttributeError: 'str' object has no attribute 'decode'

/usr/local/lib/python3.8/site-packages/fireblocks_sdk/sdk.py in get_vault_accounts(self, name_prefix, name_suffix, min_amount_threshold)
     51             url = url + "?" + urllib.parse.urlencode(params)
     52 
---> 53         return self._get_request(url)
     54 
     55     def get_vault_account(self, vault_account_id):

/usr/local/lib/python3.8/site-packages/fireblocks_sdk/sdk.py in _get_request(self, path)
   1021 
   1022     def _get_request(self, path):
-> 1023         token = self.token_provider.sign_jwt(path)
   1024         headers = {
   1025             "X-API-Key": self.api_key,

/usr/local/lib/python3.8/site-packages/fireblocks_sdk/sdk_token_provider.py in sign_jwt(self, path, body_json)
     26         }
     27 
---> 28         return jwt.encode(token, key=self.private_key, algorithm="RS256").decode('utf-8')

AttributeError: 'str' object has no attribute 'decode'

Zero amount transactions

There is following if construction in create_transaction method:

if amount:
    body["amount"] = amount

In case of amount=0 (contract call for example), bool(0) returns False, so amount field will be empty.

Better to use:

if amount is not None:
    body["amount"] = amount

DestinationTransferPeerPath default id value for ONE_TIME_ADDRESS

    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_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:
            self.oneTimeAddress = one_time_address

Default value of peer_id is None . So if I try to create transaction to ONE_TIME_ADDRESS destination like this:

destination = DestinationTransferPeerPath(peer_type='ONE_TIME_ADDRESS',
                                          one_time_address={'address': destination_address})

I get the following error:
Got an error from fireblocks server: {"message":"Destination ID must not be provided for one time address transactions","code":1409}

The only way to avoid the error is to delete id attribute.
del destination.id

import failure using python3.7

I was forced to update my sdk version from 1.15.2 to 2.3.0. I am using python 3.7.12. When I try to use this updated sdk, I get the following error:

ModuleNotFoundError: No module named 'importlib.metadata'

This error occurs on line 4 in the sdk.py file. Python 3.7 does not have importlib.metadata as part of its bundled library. You can see that this package is labeled "new" in the following link for python3.8 (https://docs.python.org/3/library/importlib.metadata.html).

For the sdk to stay compatible with python3.7, I think line 4 should be changed to the following

try:
from importlib.metadata import version
except ImportError: # for Python<3.8
from importlib_metadata import version

Upgrade PyJWT dependency

Greetings. When I installed fireblocks-sdk with pip install fireblocks-sdk, I got the following error:

ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
flask-jwt-extended 4.2.1 requires PyJWT<3.0,>=2.0, but you have pyjwt 1.7.1 which is incompatible.

The error was solved when I downgrade flask-jwt-extended to version 3.25.1, but this is not what I want, since I need to use some features of flask-jwt-extended 4.x.x.

Please, upgrade fireblocks-sdk dependencies, or at least PyJWT in order to work with the latest version of each library. Thank you in advance.

It was tested in:
Python: 3.9.5
PIP: 21.1.1
OS: Windows 10 x64

ONE_TIME_ADDRESS is not defined

>>> from fireblocks_sdk import ONE_TIME_ADDRESS
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/lv/Downloads/fireblocks-sdk-py/fireblocks_sdk/__init__.py", line 1, in <module>
    from fireblocks_sdk.sdk import FireblocksSDK
  File "/home/lv/Downloads/fireblocks-sdk-py/fireblocks_sdk/sdk.py", line 6, in <module>
    from .api_types import FireblocksApiException, TRANSACTION_TYPES, TRANSACTION_STATUS_TYPES, PEER_TYPES, TransferPeerPath, DestinationTransferPeerPath, TransferTicketTerm, TRANSACTION_TRANSFER, SIGNING_ALGORITHM, UnsignedMessage, FEE_LEVEL
  File "/home/lv/Downloads/fireblocks-sdk-py/fireblocks_sdk/api_types.py", line 39, in <module>
    TRANSACTION_TYPES = (TRANSACTION_TRANSFER, TRANSACTION_MINT, TRANSACTION_BURN, TRANSACTION_SUPPLY_TO_COMPOUND, TRANSACTION_REDEEM_FROM_COMPOUND, RAW, CONTRACT_CALL, ONE_TIME_ADDRESS)
NameError: name 'ONE_TIME_ADDRESS' is not defined

Add support for request timeouts

Sometimes, your API can respond very long.

Can you pls add the timeout parameter, which we can pass to the client, so that we can limit the request time at the client side.

Would be also great to have a default client timeout for any request, which can be set for a FireblocksSDK instance.

`estimate_fee_for_transaction` doesn't support `extraParameters`

Describe the bug
In order to estimate the fee of a transaction involving multiple inputs, I need to pass the list of inputs to the estimate_fee_for_transaction, but the function doesn't have an extra_parameters argument. create_transaction has it: I think the 2 functions should have very similar signatures

Versions (please complete the following information):

  • Python Version: 3.11
  • fireblocks-sdk version: 1.20.0

Add async support

Is your feature request related to a problem? Please describe.

The library is unusable in an asynchronous application like FastAPI as it causes blocking network IO calls, which disrupts the async event loop.

Describe the solution you'd like

Implement an AsyncFireblockSDK that uses a library like httpx instead of requests.

Additional context

httpx itself has clever ways to separate the actual HTTP IO mechanism (which has to be async) from the building of the Request and Response objects themselves, so perhaps this would be pretty simple to build to mimic it's design. It also works synchronously so it is easy to replace requests with httpx entirely.

Invalid handling of `idempotency_key` in `create_vault_asset` method

Second parameter of _post_request() is body, not a idempotency_key:

def _post_request(self, path, body={}, idempotency_key=None):

So the following code is invalid:

return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}", idempotency_key)

There should be either:

return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}", {}, idempotency_key)

or

return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}", idempotency_key=idempotency_key)

cannot import name 'StrEnum' from 'enum'

Describe the bug
In Python 3.9 and fireblocks_sdk 2.5.0, I can't run any code importing anything from fireblocks_sdk.

To Reproduce
Steps to reproduce the behavior:

Run the following snippet:

from fireblocks_sdk import FireblocksSDK

with open('FIREBLOCKS_SECRET_KEY_PATH', 'r') as file:
    fireblocks = FireblocksSDK(
        private_key=file.read(),
        api_key='FIREBLOCKS_API_KEY',
        api_base_url='https://api.fireblocks.io'
    )

Expected behavior
The code runs with no exceptions.

Screenshots

image

Versions (please complete the following information):

  • Python Version: 3.9.5
  • fireblocks-sdk version: 2.5.0

Additional context
This snippet was run in Windows 11

Workaround
I downgraded fireblocks_sdk to 2.4.0 and the code runs with no exceptions.

Fireblocks SDK API Python implementation for External wallet create function has issue

Fireblocks API External wallet create API library function has issue while implementing using function

Details :- As per the REST API documentation, the parameter customerRefId is optional, but in the fireblocks_sdk library provided for python, the default value for customerRefId is not provided.

customerRefId field is mentioned as optional in documentation but inside github code it is not optional . i tried to implemnt this and it it mandatory to mention customerRefId .i have attached screen shots also for reference purpose

external wallet 1
fireblock external wallet 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.