lib/solana/spl/associated_token.ex

defmodule Solana.SPL.AssociatedToken do
  @moduledoc """
  Functions for interacting with the [Associated Token Account
  Program](https://spl.solana.com/associated-token-account).

  An associated token account's address is derived from a user's main system
  account and the token mint, which means each user can only have one associated
  token account per token.
  """
  alias Solana.{SPL.Token, Key, Instruction, Account, SystemProgram}
  import Solana.Helpers

  @doc """
  The Associated Token Account's Program ID
  """
  def id(), do: Solana.pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")

  @doc """
  Finds the token account address associated with a given owner and mint.

  This address will be unique to the mint/owner combination.
  """
  @spec find_address(mint :: Solana.key(), owner :: Solana.key()) :: {:ok, Solana.key()} | :error
  def find_address(mint, owner) do
    with true <- Ed25519.on_curve?(owner),
         {:ok, key, _} <- Key.find_address([owner, Token.id(), mint], id()) do
      {:ok, key}
    else
      _ -> :error
    end
  end

  @create_account_schema [
    payer: [
      type: {:custom, Solana.Key, :check, []},
      required: true,
      doc: "The account which will pay for the `new` account's creation"
    ],
    owner: [
      type: {:custom, Solana.Key, :check, []},
      required: true,
      doc: "The account which will own the `new` account"
    ],
    new: [
      type: {:custom, Solana.Key, :check, []},
      required: true,
      doc: "Public key of the associated token account to create"
    ],
    mint: [
      type: {:custom, Solana.Key, :check, []},
      required: true,
      doc: "The mint of the `new` account"
    ]
  ]

  @doc """
  Creates an associated token account.

  This will be owned by the `owner` regardless of who actually creates it.

  ## Options

  #{NimbleOptions.docs(@create_account_schema)}
  """
  def create_account(opts) do
    case validate(opts, @create_account_schema) do
      {:ok, params} ->
        %Instruction{
          program: id(),
          accounts: [
            %Account{key: params.payer, writable?: true, signer?: true},
            %Account{key: params.new, writable?: true},
            %Account{key: params.owner},
            %Account{key: params.mint},
            %Account{key: SystemProgram.id()},
            %Account{key: Token.id()},
            %Account{key: Solana.rent()}
          ],
          data: Instruction.encode_data([0])
        }

      error ->
        error
    end
  end
end