lib/circlex/emulator.ex

defmodule Circlex.Emulator do
  @moduledoc """
  A basic server to mimic the functionality of Circle.
  """
  require Logger

  @type opt() :: {:port, integer()} | {:initial_state, term()}
  @default_port 3333

  def emulator_config(), do: Application.get_env(:circlex_api, :emulator)

  def usdc_address(),
    do: Signet.Util.decode_hex!(Keyword.fetch!(emulator_config(), :usdc_address))

  def port(), do: Keyword.get(emulator_config(), :port, @default_port)
  def merchant_id(), do: Keyword.fetch!(emulator_config(), :merchant_id)

  @transfer_event "Transfer(address indexed from, address indexed to, uint amount)"

  def action_delay(),
    do: Keyword.fetch!(emulator_config(), :action_delay_ms)

  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start, [opts]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  @spec start() :: {:ok, pid()} | {:error, String.t()}
  @spec start([opt()]) :: {:ok, pid()} | {:error, String.t()}
  def start(opts \\ []) do
    emulator_port = Keyword.get(opts, :port, port())
    initial_state = Keyword.get(opts, :initial_state, emulator_config()[:initial_state])
    state_name = Keyword.get(opts, :state_name, Circlex.Emulator.State)
    cowboy_ref = Module.concat(__MODULE__, "Port_" <> to_string(emulator_port))
    next = Keyword.get(opts, :next, %{})
    listeners = Keyword.get(opts, :listeners, [])
    ethereum_node = Signet.Application.ethereum_node()
    signer = Keyword.get(opts, :signer, emulator_config()[:signer] || Signet.Signer.Default)

    children = [
      {Plug.Cowboy,
       scheme: :http,
       plug: {Circlex.Emulator.Router, [state_pid: state_name, signer_proc: signer]},
       options: [ref: cowboy_ref, port: emulator_port]},
      {Circlex.Emulator.State,
       name: state_name, initial_state: initial_state, next: next, signer_proc: signer},
      {Signet.Filter,
       [name: USDCDepositFilter, address: usdc_address(), events: [@transfer_event]]},
      {Circlex.Emulator.DepositDetector, [[USDCDepositFilter], state_name]},
      {Circlex.Emulator.Notifier, listeners: listeners}
    ]

    Logger.info(
      "Circlex Emulator starting on port #{emulator_port} connected to #{ethereum_node}..."
    )

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end