1 """Config flow for Coinbase integration."""
3 from __future__
import annotations
8 from coinbase.rest
import RESTClient
9 from coinbase.rest.rest_base
import HTTPError
10 from coinbase.wallet.client
import Client
as LegacyClient
11 from coinbase.wallet.error
import AuthenticationError
12 import voluptuous
as vol
25 from .
import get_accounts
33 CONF_EXCHANGE_PRECISION,
34 CONF_EXCHANGE_PRECISION_DEFAULT,
41 _LOGGER = logging.getLogger(__name__)
43 STEP_USER_DATA_SCHEMA = vol.Schema(
45 vol.Required(CONF_API_KEY): str,
46 vol.Required(CONF_API_TOKEN): str,
52 """Get the user name from Coinbase API credentials."""
53 if "organizations" not in api_key:
54 client = LegacyClient(api_key, api_token)
55 return client.get_current_user()[
"name"]
56 client = RESTClient(api_key=api_key, api_secret=api_token)
57 return client.get_portfolios()[
"portfolios"][0][
"name"]
61 """Validate the credentials."""
64 user = await hass.async_add_executor_job(
65 get_user_from_client, data[CONF_API_KEY], data[CONF_API_TOKEN]
67 except (AuthenticationError, HTTPError)
as error:
68 if "api key" in str(error)
or " 401 Client Error" in str(error):
69 _LOGGER.debug(
"Coinbase rejected API credentials due to an invalid API key")
70 raise InvalidKey
from error
71 if "invalid signature" in str(
73 )
or "'Could not deserialize key data" in str(error):
75 "Coinbase rejected API credentials due to an invalid API secret"
77 raise InvalidSecret
from error
78 _LOGGER.debug(
"Coinbase rejected API credentials due to an unknown error")
79 raise InvalidAuth
from error
80 except ConnectionError
as error:
81 raise CannotConnect
from error
82 api_version =
"v3" if "organizations" in data[CONF_API_KEY]
else "v2"
83 return {
"title": user,
"api_version": api_version}
87 """Validate the requested resources are provided by API."""
89 client = hass.data[DOMAIN][config_entry.entry_id].client
91 accounts = await hass.async_add_executor_job(
92 get_accounts, client, config_entry.data.get(
"api_version",
"v2")
95 accounts_currencies = [
96 account[API_ACCOUNT_CURRENCY]
97 for account
in accounts
98 if not account[ACCOUNT_IS_VAULT]
100 if config_entry.data.get(
"api_version",
"v2") ==
"v2":
101 available_rates = await hass.async_add_executor_job(client.get_exchange_rates)
103 resp = await hass.async_add_executor_job(client.get,
"/v2/exchange-rates")
104 available_rates = resp[API_DATA]
105 if CONF_CURRENCIES
in options:
106 for currency
in options[CONF_CURRENCIES]:
107 if currency
not in accounts_currencies:
108 raise CurrencyUnavailable
110 if CONF_EXCHANGE_RATES
in options:
111 for rate
in options[CONF_EXCHANGE_RATES]:
112 if rate
not in available_rates[API_RATES]:
113 raise ExchangeRateUnavailable
119 """Handle a config flow for Coinbase."""
124 self, user_input: dict[str, str] |
None =
None
125 ) -> ConfigFlowResult:
126 """Handle the initial step."""
127 errors: dict[str, str] = {}
128 if user_input
is None:
130 step_id=
"user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
137 except CannotConnect:
138 errors[
"base"] =
"cannot_connect"
140 errors[
"base"] =
"invalid_auth_key"
141 except InvalidSecret:
142 errors[
"base"] =
"invalid_auth_secret"
144 errors[
"base"] =
"invalid_auth"
146 _LOGGER.exception(
"Unexpected exception")
147 errors[
"base"] =
"unknown"
149 user_input[CONF_API_VERSION] = info[
"api_version"]
152 step_id=
"user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
158 config_entry: ConfigEntry,
159 ) -> OptionsFlowHandler:
160 """Get the options flow for this handler."""
165 """Handle a option flow for Coinbase."""
168 self, user_input: dict[str, Any] |
None =
None
169 ) -> ConfigFlowResult:
170 """Manage the options."""
177 CONF_EXCHANGE_PRECISION, CONF_EXCHANGE_PRECISION_DEFAULT
180 if user_input
is not None:
182 if CONF_CURRENCIES
in user_input:
183 default_currencies = user_input[CONF_CURRENCIES]
185 if CONF_EXCHANGE_RATES
in user_input:
186 default_exchange_rates = user_input[CONF_EXCHANGE_RATES]
188 if CONF_EXCHANGE_RATES
in user_input:
189 default_exchange_base = user_input[CONF_EXCHANGE_BASE]
191 if CONF_EXCHANGE_PRECISION
in user_input:
192 default_exchange_precision = user_input[CONF_EXCHANGE_PRECISION]
196 except CurrencyUnavailable:
197 errors[
"base"] =
"currency_unavailable"
198 except ExchangeRateUnavailable:
199 errors[
"base"] =
"exchange_rate_unavailable"
201 _LOGGER.exception(
"Unexpected exception")
202 errors[
"base"] =
"unknown"
208 data_schema=vol.Schema(
212 default=default_currencies,
213 ): cv.multi_select(WALLETS),
216 default=default_exchange_rates,
217 ): cv.multi_select(RATES),
220 default=default_exchange_base,
223 CONF_EXCHANGE_PRECISION, default=default_exchange_precision
232 """Error to indicate we cannot connect."""
235 class InvalidAuth(HomeAssistantError):
236 """Error to indicate there is invalid auth."""
240 """Error to indicate auth failed due to invalid secret."""
244 """Error to indicate auth failed due to invalid key."""
248 """Error to indicate Coinbase API Key is already configured."""
252 """Error to indicate the requested currency resource is not provided by the API."""
256 """Error to indicate the requested exchange rate resource is not provided by the API."""
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
def get_user_from_client(api_key, api_token)
def validate_api(HomeAssistant hass, data)
def validate_options(HomeAssistant hass, ConfigEntry config_entry, options)