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