lib/on_flow/credentials.ex

defmodule OnFlow.Credentials do
  @moduledoc """
  Defines a struct that contains an address, a public key, and a private key.

  `:address` might be `nil`, but the key will be present in the struct.

  Do not initialize this directly. Instead, call `Credentials.new/1` or
  `Credentials.new!/1`.
  """

  @enforce_keys [:address, :public_key, :private_key]
  defstruct [:address, :public_key, :private_key]

  @typep hex_string() :: String.t()

  @type keys_with_address() :: %{
          optional(:address) => nil | String.t(),
          required(:public_key) => String.t(),
          required(:private_key) => String.t()
        }

  @type t() :: %__MODULE__{
          address: nil | hex_string(),
          public_key: hex_string(),
          private_key: hex_string()
        }

  @doc """
  Initializes a `%Credentials{}` struct. Returns `{:ok, credentials}` on
  success. The params _must_ contain a `:private_key` and `:public_key`, or
  `{:error, :missing_keys}` will be returned.

  `:public_key` and `:private_key` must be hex-encoded strings.
  """
  @spec new(keys_with_address()) :: {:ok, t()} | {:error, :missing_keys}
  def new(attrs) do
    address =
      case Map.get(attrs, :address) do
        address when is_binary(address) or is_nil(address) -> address
        _ -> nil
      end

    with public_key when is_binary(public_key) <- Map.get(attrs, :public_key),
         private_key when is_binary(private_key) <- Map.get(attrs, :private_key) do
      credentials = %__MODULE__{
        address: address,
        public_key: public_key,
        private_key: private_key
      }

      {:ok, credentials}
    else
      _ -> {:error, :missing_keys}
    end
  end

  @doc """
  Initializes a `%Credentials{}` struct. Raises on error. See the documentation
  for `new/1`.
  """
  @spec new!(map()) :: t() | none()
  def new!(attrs) do
    case new(attrs) do
      {:ok, credentials} -> credentials
      _ -> raise OnFlow.MissingKeysError, attrs
    end
  end

  @doc """
  Returns a `%Credentials{}` struct with the keypairs generated.
  """
  @spec generate_keys() :: t()
  def generate_keys do
    new!(OnFlow.Crypto.generate_keys())
  end
end