defmodule DigitalToken.Decode do @moduledoc false # This module reads the json file and formats the # data into a format useful for looking up registry # data. @priv_dir Application.app_dir(:digital_token, "priv") @tokens_file_name Path.join(@priv_dir, "digital_token_registry.etf") @symbols_file_name Path.join(@priv_dir, "digital_token_symbols.etf") @external_resource @tokens_file_name @external_resource @symbols_file_name def tokens_file_name do @tokens_file_name end def symbols_file_name do @symbols_file_name end def decode_tokens(body) do body |> json_decode!() |> Map.fetch!("records") |> Enum.filter(&has_dti?/1) |> Enum.map(&restructure_key/1) |> merge_map_list() end def decode_symbols(body) do body |> json_decode!() |> Enum.map(fn token -> symbol_name = token["symbol"] unicode_symbol = token["usym"] token_types = [:native, :auxiliary, :distributed, :fungible] Enum.find_value(token_types, nil, fn type -> if token_id = Map.get(DigitalToken.Data.short_names(), {symbol_name, type}) do {token_id, unicode_symbol} else nil end end) end) |> Enum.reject(&is_nil/1) |> Map.new() end defp has_dti?(%{"Header" => header}) do Map.has_key?(header, "DTI") end def restructure_key(map) do header = Map.fetch!(map, "Header") dti = Map.fetch!(header, "DTI") map = map |> normalize_keys() |> transform_values() |> structify(DigitalToken) %{dti => map} end def short_names(data) do Enum.flat_map(data, fn {token, values} -> short_names = values.informative.short_names dti_type = Map.get(values.header, :dti_type, :native) [{{values.informative.long_name, dti_type}, token} | Enum.map(short_names, &{{&1, dti_type}, token})] end) |> Map.new() end def search_index(data) do Enum.reduce(data, %{}, fn {token_id, values}, acc -> short_names = values.informative.short_names long_name = values.informative.long_name dti_type = Map.get(values.header, :dti_type, :native) names = [long_name | short_names] |> Enum.reject(&is_nil/1) |> Enum.uniq() Enum.reduce(names, acc, fn name, inner_acc -> Map.update(inner_acc, name, [{token_id, dti_type}], &[{token_id, dti_type} | &1]) end) end) end defp merge_map_list(maps) do Enum.reduce(maps, %{}, &Map.merge(&2, &1)) end # ── Key normalization ───────────────────────────────────────── defp normalize_keys(map) when is_map(map) do map |> Enum.map(fn {key, value} -> new_key = normalize_key(key) {new_key, normalize_keys(value)} end) |> Map.new() end defp normalize_keys(list) when is_list(list) do Enum.map(list, &normalize_keys/1) end defp normalize_keys(value), do: value @key_map %{ "Header" => :header, "Informative" => :informative, "Normative" => :normative, "Metadata" => :metadata, "DTI" => :dti, "DTIType" => :dti_type, "DLTType" => :dlt_type, "LongName" => :long_name, "ShortNames" => :short_names, "ShortName" => :short_name, "UnitMultiplier" => :unit_multiplier, "URL" => :url, "PublicDistributedLedgerIndication" => :public_distributed_ledger_indication, "BlockNumberOffset" => :block_number_offset, "AnchorBlockHash" => :anchor_block_hash, "AnchorBlockHashAlgorithm" => :anchor_block_hash_algorithm, "AnchorBlockUTCTimestamp" => :anchor_block_utc_timestamp, "AnchorBlockHeight" => :anchor_block_height, "AuxiliaryDistributedLedger" => :auxiliary_distributed_ledger, "AuxiliaryMechanism" => :auxiliary_mechanism, "AuxiliaryTechnicalReference" => :auxiliary_technical_reference, "EquivalentDigitalTokenGroupDTI" => :equivalent_digital_token_group_dti, "ProtocolDistributedLedger" => :protocol_distributed_ledger, "DTIExternalIdentifiers" => :dti_external_identifiers, "DigitalAssetExternalIdentifiers" => :digital_asset_external_identifiers, "DigitalAssetExternalIdentifierType" => :digital_asset_external_identifier_type, "DigitalAssetExternalIdentifierValue" => :digital_asset_external_identifier_value, "IssuerIdentifiers" => :issuer_identifiers, "MaintainerIdentifiers" => :maintainer_identifiers, "OrigLangLongName" => :orig_lang_long_name, "recVersion" => :rec_version, "recDateTime" => :rec_date_time, "templateVersion" => :template_version, "Provisional" => :provisional, "Private" => :private, "Disputed" => :disputed, "Deleted" => :deleted, "Certified" => :certified, "Issued" => :issued, "Lapsed" => :lapsed, "Retired" => :retired } defp normalize_key(key) when is_binary(key) do Map.get(@key_map, key, underscore(key)) end defp underscore(key) do key |> String.replace(~r/([A-Z]+)([A-Z][a-z])/, "\\1_\\2") |> String.replace(~r/([a-z\d])([A-Z])/, "\\1_\\2") |> String.downcase() |> String.to_atom() end # ── Value transformations ───────────────────────────────────── defp transform_values(map) when is_map(map) do map |> Enum.reject(fn {_key, value} -> value == "" end) |> Enum.map(fn {key, value} -> {key, transform_value(key, value)} end) |> Enum.reject(fn {_key, value} -> value == :locked_removed end) |> Map.new() end defp transform_value(:dti_type, 0), do: :auxiliary defp transform_value(:dti_type, 1), do: :native defp transform_value(:dti_type, 2), do: :distributed defp transform_value(:dti_type, 3), do: :fungible defp transform_value(:dlt_type, "0"), do: :other defp transform_value(:dlt_type, "1"), do: :blockchain defp transform_value(:dlt_type, 0), do: :other defp transform_value(:dlt_type, 1), do: :blockchain defp transform_value(:template_version, "V" <> version) do Version.parse!(version) end defp transform_value(:short_names, %{short_name: name}) when is_binary(name) do [name] end defp transform_value(:short_names, names) when is_list(names) do names |> Enum.map(fn %{short_name: name} -> name name when is_binary(name) -> name end) |> Enum.sort_by(&String.length/1) end defp transform_value(:short_names, nil), do: [] defp transform_value(:anchor_block_utc_timestamp, datetime) when is_binary(datetime) do NaiveDateTime.from_iso8601!(datetime) end defp transform_value(:rec_date_time, datetime) when is_binary(datetime) do NaiveDateTime.from_iso8601!(datetime) end defp transform_value(:anchor_block_hash, "0x" <> hash) do case Integer.parse(hash, 16) do {integer_hash, ""} -> integer_hash {integer_hash, remainder} -> {integer_hash, remainder} end end defp transform_value(:auxiliary_technical_reference, "0x" <> hash) do case Integer.parse(hash, 16) do {integer_hash, ""} -> integer_hash {integer_hash, remainder} -> {integer_hash, remainder} end end defp transform_value(:unit_multiplier, value) when is_integer(value), do: value defp transform_value(_key, value) when is_map(value) do transform_values(value) end defp transform_value(_key, value) when is_list(value) do Enum.map(value, fn item when is_map(item) -> transform_values(item) item -> item end) end defp transform_value(_key, value), do: value defp structify(map, struct) do struct(struct, map) end defp json_decode!(binary) do :json.decode(binary) end end