lib/bsv/utxo.ex

defmodule BSV.UTXO do
  @moduledoc """
  A UTXO is a data structure representing an unspent transaction output.

  A UTXO consists of a `t:BSV.OutPoint.t/0` and the `t:BSV.TxOut.t/0` itself.
  UTXOs are used in the `BSV.TxBuilder` module to create transaction inputs.
  """
  alias BSV.{OutPoint, Script, Tx, TxOut}
  import BSV.Util, only: [decode: 2, reverse_bin: 1]

  defstruct outpoint: nil, txout: nil

  @typedoc "UTXO struct"
  @type t() :: %__MODULE__{
    outpoint: OutPoint.t(),
    txout: TxOut.t()
  }

  @doc """
  Builds a `t:BSV.UTXO.t/0` from the given map of params. Useful for building
  UTXO's from JSON APIs.

  Returns the result in an `:ok` / `:error` tuple pair.

  ## Params

  The required params are:

  * `txid` - Transaction ID
  * `vout` - Vector of the output in a transaction. Also accepts `outputIndex`
  * `satoshis` - Number of satoshis. Also accepts `amount`
  * `script` - Hex-encoded locking script

  ## Examples

      iex> UTXO.from_params(%{
      ...>   "txid" => "5e3014372338f079f005eedc85359e4d96b8440e7dbeb8c35c4182e0c19a1a12",
      ...>   "vout" => 0,
      ...>   "satoshis" => 15399,
      ...>   "script" => "76a91410bdcba3041b5e5517a58f2e405293c14a7c70c188ac"
      ...> })
      {:ok, %UTXO{
        outpoint: %OutPoint{
          hash: <<18, 26, 154, 193, 224, 130, 65, 92, 195, 184, 190, 125, 14, 68, 184, 150, 77, 158, 53, 133, 220, 238, 5, 240, 121, 240, 56, 35, 55, 20, 48, 94>>,
          vout: 0
        },
        txout: %TxOut{
          satoshis: 15399,
          script: %Script{chunks: [
            :OP_DUP,
            :OP_HASH160,
            <<16, 189, 203, 163, 4, 27, 94, 85, 23, 165, 143, 46, 64, 82, 147, 193, 74, 124, 112, 193>>,
            :OP_EQUALVERIFY,
            :OP_CHECKSIG
          ]}
        }
      }}
  """
  @spec from_params(map()) :: {:ok, t()} | {:error, term()}
  def from_params(%{"txid" => txid, "script" => script} = params) do
    with {:ok, hash} <- decode(txid, :hex),
         {:ok, vout} <- take_any_param(params, ["vout", "outputIndex"]),
         {:ok, satoshis} <- take_any_param(params, ["satoshis", "amount"]),
         {:ok, script} <- Script.from_binary(script, encoding: :hex)

    do
      outpoint = struct(OutPoint, hash: reverse_bin(hash), vout: vout)
      txout = struct(TxOut, satoshis: satoshis, script: script)
      {:ok, struct(__MODULE__, outpoint: outpoint, txout: txout)}
    end
  end

  @doc """
  Builds a `t:BSV.UTXO.t/0` from the given map of params.

  As `from_params/1` but returns the result or raises an exception.
  """
  @spec from_params!(map()) :: t()
  def from_params!(%{} = params) do
    case from_params(params) do
      {:ok, utxo} ->
        utxo

      {:error, error} ->
        raise BSV.DecodeError, error
    end
  end

  @doc """
  Builds a `t:BSV.UTXO.t/0` from the given transaction and vout index. Useful
  for building UTXO's when you already have the full transaction being spent
  from.
  """
  @spec from_tx(Tx.t(), TxOut.vout()) :: TxOut.t() | nil
  def from_tx(%Tx{outputs: outputs} = tx, vout) when vout < length(outputs) do
    with %TxOut{} = txout <- Enum.at(outputs, vout) do
      outpoint = %OutPoint{hash: Tx.get_hash(tx), vout: vout}
      %__MODULE__{outpoint: outpoint, txout: txout}
    end
  end

  # Takes the first value from the list of keys on the given map of params
  defp take_any_param(params, keys) do
    case Map.take(params, keys) |> Map.values() do
      [value | _] ->
        {:ok, value}
      _ ->
        {:error, {:param_not_found, keys}}
    end
  end

end