defmodule DigitalToken do @moduledoc """ Functions to validate, search and retrieve digital token data sourced from the [DTIF registry](https://dtif.org). """ defstruct [ header: nil, informative: nil, metadata: nil, normative: nil ] @typedoc """ The structure of data matching that hosted in the [DTIF registry](https://dtif.org)/ """ @type t :: %__MODULE__{ header: map(), informative: map(), metadata: map(), normative: map() } @typedoc """ token_id is a random 9-character string defined by the [dtif registry](https://dtif.org) that uniquely identifies a digital token. """ @type token_id :: String.t() @typedoc """ A digital token may have zero of more short names associated with it. They are arbitrary strings, usually three or four characters in length. For example "BTC", "ETH" and "DOGE". """ @type short_name :: String.t() @typedoc """ A mapping of digital token identifiers to the registry data for that token. """ @type token_map :: %{ token_id() => t() } @typedoc """ A mapping from a short name to token identifier. """ @type short_name_map :: %{ short_name() => token_id() } @typedoc """ A mapping of digital token identifiers to a currency symbol for that token. """ @type symbol_map :: %{ token_id() => String.t() } defguard is_digital_token(token_id) when is_binary(token_id) and byte_size(token_id) == 9 @doc """ Returns a map of the digital tokens in the [dtif registry](https://dtif.org). """ @spec tokens :: token_map() def tokens do DigitalToken.Data.tokens() end @doc """ Returns a mapping of digital token short names to digital token identifiers. """ @spec short_names :: short_name_map() def short_names do DigitalToken.Data.short_names() end @doc """ Returns a mapping of digital token identifiers names to a currency symbol. """ @spec symbols :: symbol_map() def symbols do DigitalToken.Data.symbols() end @doc """ Validates a token identifier or short name and returns the token identifier or an error. ## Arguments * `id` is any token identifier or short name ## Returns * `{:ok, token_id}` or * `{:error, {exception, id}}` ## Example iex> DigitalToken.validate_token("BTC") {:ok, "4H95J0R2X"} iex> DigitalToken.validate_token("Bitcoin") {:ok, "4H95J0R2X"} iex> DigitalToken.validate_token "4H95J0R2X" {:ok, "4H95J0R2X"} iex> DigitalToken.validate_token("Nothing") {:error, {DigitalToken.UnknownTokenError, "Nothing"}} """ @spec validate_token(token_id() | short_name()) :: {:ok, token_id()} | {:error, {module(), any()}} def validate_token(id) do cond do Map.has_key?(tokens(), id) -> {:ok, id} token = Map.get(short_names(), id) -> {:ok, token} id -> {:error, unknown_token_error(id)} end end @doc """ Returns the short name of a digital token. ## Arguments * `token_id` is any validate digitial token identifier. ## Returns * `{:ok, short_name}` where `short_name` is either the first short name in `token.informative.short_names` or the token long name if there are no short names. * `{:error, {exceoption, reason}}` ## Examples iex> DigitalToken.short_name "BTC" {:ok, "BTC"} iex> DigitalToken.short_name "4H95J0R2X" {:ok, "BTC"} iex> DigitalToken.short_name "W0HBX7RC4" {:ok, "Terra"} """ @spec short_name(token_id) :: {:ok, String.t()} | {:error, {module(), String.t}} def short_name(token_id) do with {:ok, token} <- get_token(token_id) do case Map.get(token.informative, :short_names) do [first | _rest] -> {:ok, first} _other -> {:ok, token.informative.long_name} end end end @doc """ Returns the long name of a digital token. ## Arguments * `token_id` is any validate digitial token identifier. ## Returns * `{:ok, long_name}` where `long_name` is the token's registered name. * `{:error, {exceoption, reason}}` ## Examples iex> DigitalToken.long_name "BTC" {:ok, "Bitcoin"} iex> DigitalToken.long_name "4H95J0R2X" {:ok, "Bitcoin"} iex> DigitalToken.long_name "W0HBX7RC4" {:ok, "Terra"} """ @spec long_name(token_id) :: {:ok, String.t()} | {:error, {module(), String.t}} def long_name(token_id) do with {:ok, token} <- get_token(token_id) do Map.fetch(token.informative, :long_name) end end @doc """ Returns a currency symbol used in number formatting. ## Arguements * `token_id` is any validate digitial token identifier. * `style` is a number in the range `1` to `4` as follows: * `1` is the token's symbol, if it exists * `2` is the token's short name as a proxy for a currency code * `3` is the token's long name * `4` is the token's symbol as a proxy for a narrow currency symbol ## Returns * `{:ok, symbol}` or * `{:error, {exception, reason}}` ## Examples iex> DigitalToken.symbol "BTC", 1 {:ok, "₿"} iex> DigitalToken.symbol "BTC", 2 {:ok, "BTC"} iex> DigitalToken.symbol "BTC", 3 {:ok, "Bitcoin"} iex> DigitalToken.symbol "BTC", 4 {:ok, "₿"} iex> DigitalToken.symbol "ETH", 4 {:ok, "Ξ"} iex> DigitalToken.symbol "DOGE", 4 {:ok, "Ð"} iex> DigitalToken.symbol "DODGY", 4 {:error, {DigitalToken.UnknownTokenError, "DODGY"}} """ @spec symbol(token_id, 1..4) :: {:ok, String.t()} | {:error, {module(), String.t}} def symbol(token_id, style) when style in [1, 4] do with {:ok, token_id} <- validate_token(token_id), {:ok, symbol} <- Map.fetch(symbols(), token_id) do {:ok, symbol} else :error -> short_name(token_id) other_error -> other_error end end def symbol(token_id, 2) do short_name(token_id) end def symbol(token_id, 3) do long_name(token_id) end @doc """ Returns the registry data for a given token identifier. ## Arugments * `id` is any token identifier or short name ## Returns * `{:ok, registry_data}` or * `{:error, {exception, id}}` ## Example DigitalToken.get_token("BTC") #=> {:ok, %DigitalToken{ header: %{ dlt_type: :blockchain, dti: "4H95J0R2X", dti_type: :native, template_version: #Version<1.0.0> }, informative: %{ long_name: "Bitcoin", public_distributed_ledger_indication: false, short_names: ["BTC", "XBT"], unit_multiplier: 100000000, url: "https://github.com/bitcoin/bitcoin" }, metadata: %{ ..... """ @spec get_token(token_id() | short_name()) :: {:ok, t()} | {:error, {module(), any()}} def get_token(id) do with {:ok, token} <- validate_token(id) do {:ok, Map.fetch!(tokens(), token)} end end @doc """ Returns the registry data for a given token identifier. ## Arugments * `id` is any token identifier or short name ## Returns * `registry_data` or * raises an exception ## Example DigitalToken.get_token("BTC") #=> %DigitalToken{ header: %{ dlt_type: :blockchain, dti: "4H95J0R2X", dti_type: :native, template_version: #Version<1.0.0> }, informative: %{ long_name: "Bitcoin", public_distributed_ledger_indication: false, short_names: ["BTC", "XBT"], unit_multiplier: 100000000, url: "https://github.com/bitcoin/bitcoin" }, metadata: %{ ..... """ @spec get_token!(token_id() | short_name()) :: t() | no_return() def get_token!(id) do case get_token(id) do {:ok, token} -> token {:error, {exception, id}} -> raise exception, id end end defp unknown_token_error(id) do {DigitalToken.UnknownTokenError, id} end end