Current section

Files

Jump to
forge lib utils wallet.ex
Raw

lib/utils/wallet.ex

defmodule Forge.Wallet do
@moduledoc """
Wallet client
"""
require Logger
alias Forge.{AccountState, Crypto, Helper, Rpc, Server, Tx.TransferTransaction}
@typedoc """
Ethereum-compatible wallet. It contains keys as folows:
* `:private_key`: Base16 encoded private key.
* `:public_key`: Base16 encoded public key.
* `:address`: wallet address.
"""
@type t :: %{
private_key: String.t(),
public_key: String.t(),
address: String.t()
}
@doc """
Create a new eth-like wallet
"""
@spec new() :: t()
def new do
private_key = Crypto.create_private_key()
public_key = Crypto.create_public_key(private_key)
address = Crypto.create_wallet_address(public_key)
%{
private_key: Base.encode16(private_key),
public_key: Base.encode16(public_key),
address: address
}
end
@spec save(t(), binary()) :: :ok
def save(wallet, passphrase) do
path = Helper.get_key_store_path()
File.mkdir_p!(path)
filename = Path.join(path, wallet.address)
[iv: iv, ciphertext: cipher] = Crypto.block_encrypt(wallet.private_key, passphrase)
data = Map.merge(wallet, %{private_key: cipher, iv: Base.encode64(iv)})
File.write!(filename, Jason.encode!(data))
end
@spec load(binary() | map() | any(), binary()) :: :error | t()
def load(address, passphrase) when is_binary(address) do
path = Helper.get_key_store_path()
filename = Path.join(path, address)
case File.exists?(filename) do
true ->
content = filename |> File.read!() |> Jason.decode!(keys: :atoms!)
load(content, passphrase)
_ ->
:error
end
end
def load(content, passphrase) when is_map(content) do
%{address: address, private_key: cipher, public_key: public_key, iv: iv} = content
private_key = Crypto.block_decrypt(cipher, passphrase, Base.decode64!(iv))
case private_key do
:error ->
:error
_ ->
%{
private_key: private_key,
public_key: public_key,
address: address
}
end
rescue
e ->
Logger.error(e)
:error
end
def load(_, _), do: :error
@doc """
Transfer token from one address to another
"""
@spec transfer(t(), String.t(), non_neg_integer()) :: :ok | {:error, any}
def transfer(%{address: from, public_key: public_key, private_key: private_key}, to, total) do
nonce = nonce(from)
from
|> TransferTransaction.create(to, nonce, total, public_key, private_key)
|> Rpc.send()
end
@doc """
Retrieve the current balance of the address
"""
@spec balance(String.t()) :: non_neg_integer()
def balance(address) do
apply(Server, :account_balance, [address])
end
@doc """
Retrieve the current nonce of the address
"""
@spec nonce(String.t()) :: non_neg_integer()
def nonce(address) do
apply(Server, :account_nonce, [address])
end
@doc """
Retrieve the account info of the address
"""
@spec info(String.t(), String.t() | non_neg_integer()) :: AccountState.t() | nil
def info(address, app_hash \\ "") do
apply(Server, :account_info, [address, app_hash])
end
@doc """
Retrieve the chain info
"""
@spec chain_info :: map()
def chain_info do
apply(Server, :chain_info, [])
end
end