defmodule Stellar.TxBuild.Account do
@moduledoc """
`Account` struct definition.
"""
alias Stellar.KeyPair
alias StellarBase.XDR.{CryptoKeyType, MuxedAccount, MuxedAccountMed25519, UInt64, UInt256}
@behaviour Stellar.TxBuild.XDR
@type address :: String.t()
@type account_id :: String.t()
@type muxed_id :: integer() | nil
@type type :: :ed25519_public_key | :ed25519_muxed_account
@type error :: {:error, atom()}
@type validation :: {:ok, any()} | error()
@type t :: %__MODULE__{
address: address(),
account_id: account_id(),
muxed_id: muxed_id(),
type: type()
}
defstruct [:address, :account_id, :muxed_id, :type]
@impl true
def new(address, opts \\ [])
def new(address, _opts) when byte_size(address) == 69 do
with {:ok, address} <- validate_muxed_address(address),
{:ok, {account_id, muxed_id}} <- parse_muxed_address(address) do
%__MODULE__{
address: address,
account_id: account_id,
muxed_id: muxed_id,
type: :ed25519_muxed_account
}
end
end
def new(address, _opts) when byte_size(address) == 56 do
case validate_public_key(address) do
{:ok, account_id} ->
%__MODULE__{
address: account_id,
account_id: account_id,
type: :ed25519_public_key
}
error ->
error
end
end
def new(_address, _opts), do: {:error, :invalid_ed25519_public_key}
@impl true
def to_xdr(%__MODULE__{account_id: account_id, muxed_id: muxed_id, type: :ed25519_muxed_account}) do
type = CryptoKeyType.new(:KEY_TYPE_MUXED_ED25519)
ed25519_public_key_xdr = ed25519_public_key_xdr(account_id)
muxed_id
|> UInt64.new()
|> MuxedAccountMed25519.new(ed25519_public_key_xdr)
|> MuxedAccount.new(type)
end
def to_xdr(%__MODULE__{account_id: account_id}) do
type = CryptoKeyType.new(:KEY_TYPE_ED25519)
ed25519_public_key_xdr = ed25519_public_key_xdr(account_id)
MuxedAccount.new(ed25519_public_key_xdr, type)
end
@spec create_muxed(account_id :: account_id(), muxed_id :: muxed_id()) :: t()
def create_muxed(account_id, muxed_id)
when byte_size(account_id) == 56 and is_integer(muxed_id) do
account_id
|> KeyPair.raw_public_key()
|> Kernel.<>(<<muxed_id::big-unsigned-integer-size(64)>>)
|> KeyPair.from_raw_muxed_account()
|> new()
end
def create_muxed(_account_id, _id), do: {:error, :invalid_muxed_account}
@spec validate_muxed_address(address :: address()) :: validation()
defp validate_muxed_address(address) do
case KeyPair.validate_muxed_account(address) do
:ok -> {:ok, address}
error -> error
end
end
@spec validate_public_key(address :: address()) :: validation()
defp validate_public_key(address) do
case KeyPair.validate_public_key(address) do
:ok -> {:ok, address}
error -> error
end
end
@spec parse_muxed_address(address :: address()) :: {:ok, {account_id(), muxed_id()}} | error()
defp parse_muxed_address(address) do
address
|> KeyPair.raw_muxed_account()
|> encode_muxed_address()
end
@spec encode_muxed_address(decoded_address :: binary()) ::
{:ok, {account_id(), muxed_id()}} | error()
defp encode_muxed_address(<<decoded::binary-size(32), muxed_id::big-unsigned-integer-size(64)>>) do
ed25519_key = KeyPair.from_raw_public_key(decoded)
{:ok, {ed25519_key, muxed_id}}
end
defp encode_muxed_address(_muxed_account), do: {:error, :invalid_muxed_address}
@spec ed25519_public_key_xdr(account_id :: account_id()) :: UInt256.t()
defp ed25519_public_key_xdr(account_id) do
account_id
|> KeyPair.raw_public_key()
|> UInt256.new()
end
end