lib/key/hd/derivation_path/parser/coin_type.ex

defmodule BitcoinLib.Key.HD.DerivationPath.Parser.CoinType do
  @moduledoc """
  Determines which coin type is being used, either bitcoin mainnet or bitcoin testnet.

  This is a hardened value.

  See: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#registered-coin-types
  """

  @bitcoin_coin_type "0'"
  @bitcoin_value 0
  @bitcoin_atom :bitcoin

  @bitcoin_testnet_coin_type "1'"
  @bitcoin_testnet_value 1
  @bitcoin_testnet_atom :bitcoin_testnet

  @doc """
  Converts a list of path levels managed up until the coin type, extracts
  the coin type and returns the remaining levels

  ## Examples
      iex> ["1'", "1", "2", "3"]
      ...> |> BitcoinLib.Key.HD.DerivationPath.Parser.CoinType.extract()
      {:ok, :bitcoin_testnet, ["1", "2", "3"]}
  """
  @spec extract(list()) :: {:ok, nil, []} | {:ok, atom(), list()} | {:error, binary()}
  def extract([]), do: {:ok, nil, []}
  def extract([@bitcoin_coin_type | rest]), do: {:ok, @bitcoin_atom, rest}
  def extract([@bitcoin_testnet_coin_type | rest]), do: {:ok, @bitcoin_testnet_atom, rest}

  def extract([coin_type | _rest]) do
    message =
      case String.ends_with?(coin_type, "'") do
        true -> "#{coin_type} is an invalid coin type"
        false -> "coin type must be a hardened value"
      end

    {:error, message}
  end

  def get_atom(@bitcoin_value), do: @bitcoin_atom
  def get_atom(@bitcoin_testnet_value), do: @bitcoin_testnet_atom
end