Packages

Ethereum library for Gleam - JSON-RPC client, transaction signing, ABI encoding, and wallet management on the BEAM

Current section

Files

Jump to
gleeth src gleeth@crypto@wallet.erl
Raw

src/gleeth@crypto@wallet.erl

-module(gleeth@crypto@wallet).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/crypto/wallet.gleam").
-export([from_private_key_hex/1, from_private_key_bytes/1, generate/0, get_address/1, get_private_key_hex/1, get_public_key_hex/1, get_private_key_bytes/1, get_public_key_bytes/1, is_valid/1, sign_hash/2, sign_message/2, sign_personal_message/2, recover_personal_message/2, verify_personal_message/3, to_string/1, to_summary/1, equals/2, error_to_string/1, is_recoverable_error/1]).
-export_type([wallet/0, wallet_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type wallet() :: {wallet,
gleeth@crypto@secp256k1:private_key(),
gleeth@crypto@secp256k1:public_key(),
gleeth@crypto@secp256k1:ethereum_address()}.
-type wallet_error() :: {invalid_private_key, binary()} |
{invalid_hex, binary()} |
{key_generation_failed, binary()} |
{signing_failed, binary()}.
-file("src/gleeth/crypto/wallet.gleam", 31).
?DOC(
" Create a wallet from a private key hex string\n"
" Supports both with and without 0x prefix\n"
).
-spec from_private_key_hex(binary()) -> {ok, wallet()} | {error, wallet_error()}.
from_private_key_hex(Hex_string) ->
gleam@result:'try'(
begin
_pipe = gleeth@crypto@secp256k1:private_key_from_hex(Hex_string),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {invalid_private_key, Field@0} end
)
end,
fun(Private_key) ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@crypto@secp256k1:create_public_key(
Private_key
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Public_key) ->
gleam@result:'try'(
begin
_pipe@2 = gleeth@crypto@secp256k1:public_key_to_address(
Public_key
),
gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Address) ->
{ok, {wallet, Private_key, Public_key, Address}}
end
)
end
)
end
).
-file("src/gleeth/crypto/wallet.gleam", 51).
?DOC(" Create a wallet from raw private key bytes\n").
-spec from_private_key_bytes(bitstring()) -> {ok, wallet()} |
{error, wallet_error()}.
from_private_key_bytes(Bytes) ->
gleam@result:'try'(
begin
_pipe = gleeth@crypto@secp256k1:private_key_from_bytes(Bytes),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {invalid_private_key, Field@0} end
)
end,
fun(Private_key) ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@crypto@secp256k1:create_public_key(
Private_key
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Public_key) ->
gleam@result:'try'(
begin
_pipe@2 = gleeth@crypto@secp256k1:public_key_to_address(
Public_key
),
gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Address) ->
{ok, {wallet, Private_key, Public_key, Address}}
end
)
end
)
end
).
-file("src/gleeth/crypto/wallet.gleam", 71).
?DOC(" Generate a new random wallet using cryptographically secure randomness\n").
-spec generate() -> {ok, wallet()} | {error, wallet_error()}.
generate() ->
gleam@result:'try'(
begin
_pipe = gleeth@crypto@random:generate_private_key(),
gleam@result:map_error(
_pipe,
fun(Err) ->
{key_generation_failed,
<<"Failed to generate secure private key: "/utf8,
(gleeth@crypto@random:error_to_string(Err))/binary>>}
end
)
end,
fun(Private_key) ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@crypto@secp256k1:create_public_key(
Private_key
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Public_key) ->
gleam@result:'try'(
begin
_pipe@2 = gleeth@crypto@secp256k1:public_key_to_address(
Public_key
),
gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {key_generation_failed, Field@0} end
)
end,
fun(Address) ->
{ok, {wallet, Private_key, Public_key, Address}}
end
)
end
)
end
).
-file("src/gleeth/crypto/wallet.gleam", 99).
?DOC(" Get the wallet's Ethereum address as a string\n").
-spec get_address(wallet()) -> binary().
get_address(Wallet) ->
gleeth@crypto@secp256k1:address_to_string(erlang:element(4, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 104).
?DOC(" Get the wallet's private key as a hex string\n").
-spec get_private_key_hex(wallet()) -> binary().
get_private_key_hex(Wallet) ->
gleeth@crypto@secp256k1:private_key_to_hex(erlang:element(2, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 109).
?DOC(" Get the wallet's public key as a hex string\n").
-spec get_public_key_hex(wallet()) -> binary().
get_public_key_hex(Wallet) ->
gleeth@crypto@secp256k1:public_key_to_hex(erlang:element(3, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 114).
?DOC(" Get the wallet's private key as raw bytes\n").
-spec get_private_key_bytes(wallet()) -> bitstring().
get_private_key_bytes(Wallet) ->
gleeth@crypto@secp256k1:private_key_to_bytes(erlang:element(2, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 119).
?DOC(" Get the wallet's public key as raw bytes\n").
-spec get_public_key_bytes(wallet()) -> bitstring().
get_public_key_bytes(Wallet) ->
gleeth@crypto@secp256k1:public_key_to_bytes(erlang:element(3, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 124).
?DOC(" Check if the wallet has a valid private key\n").
-spec is_valid(wallet()) -> boolean().
is_valid(Wallet) ->
gleeth@crypto@secp256k1:is_valid_private_key(erlang:element(2, Wallet)).
-file("src/gleeth/crypto/wallet.gleam", 133).
?DOC(" Sign a message hash with the wallet's private key\n").
-spec sign_hash(wallet(), bitstring()) -> {ok,
gleeth@crypto@secp256k1:signature()} |
{error, wallet_error()}.
sign_hash(Wallet, Message_hash) ->
_pipe = gleeth@crypto@secp256k1:sign_message_hash(
Message_hash,
erlang:element(2, Wallet)
),
gleam@result:map_error(_pipe, fun(Field@0) -> {signing_failed, Field@0} end).
-file("src/gleeth/crypto/wallet.gleam", 142).
?DOC(" Sign raw message bytes (will be hashed with keccak256)\n").
-spec sign_message(wallet(), bitstring()) -> {ok,
gleeth@crypto@secp256k1:signature()} |
{error, wallet_error()}.
sign_message(Wallet, Message) ->
_pipe = gleeth@crypto@secp256k1:sign_message(
Message,
erlang:element(2, Wallet)
),
gleam@result:map_error(_pipe, fun(Field@0) -> {signing_failed, Field@0} end).
-file("src/gleeth/crypto/wallet.gleam", 151).
?DOC(" Sign an Ethereum personal message\n").
-spec sign_personal_message(wallet(), binary()) -> {ok,
gleeth@crypto@secp256k1:signature()} |
{error, wallet_error()}.
sign_personal_message(Wallet, Message) ->
_pipe = gleeth@crypto@secp256k1:sign_personal_message(
Message,
erlang:element(2, Wallet)
),
gleam@result:map_error(_pipe, fun(Field@0) -> {signing_failed, Field@0} end).
-file("src/gleeth/crypto/wallet.gleam", 194).
?DOC(
" Compute the EIP-191 personal message hash.\n"
" hash = keccak256(\"\\x19Ethereum Signed Message:\\n\" + len(message) + message)\n"
).
-spec personal_message_hash(binary()) -> bitstring().
personal_message_hash(Message) ->
Message_bytes = gleam_stdlib:identity(Message),
Message_length = begin
_pipe = erlang:byte_size(Message_bytes),
gleam@string:inspect(_pipe)
end,
Prefix = <<"\\x19Ethereum Signed Message:\\n"/utf8, Message_length/binary>>,
Prefix_bytes = gleam_stdlib:identity(Prefix),
Full_message = gleam@bit_array:append(Prefix_bytes, Message_bytes),
gleeth@crypto@keccak:keccak256_binary(Full_message).
-file("src/gleeth/crypto/wallet.gleam", 166).
?DOC(
" Recover the signer address from an EIP-191 personal message signature.\n"
" Applies the standard prefix before recovery, matching what MetaMask\n"
" and other wallets produce with personal_sign.\n"
).
-spec recover_personal_message(binary(), binary()) -> {ok, binary()} |
{error, wallet_error()}.
recover_personal_message(Message, Signature_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@crypto@secp256k1:signature_from_hex(Signature_hex),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {invalid_hex, Field@0} end
)
end,
fun(Signature) ->
Message_hash = personal_message_hash(Message),
gleam@result:'try'(
begin
_pipe@1 = gleeth@crypto@secp256k1:recover_address(
Message_hash,
Signature
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {signing_failed, Field@0} end
)
end,
fun(Address) ->
{ok, gleeth@crypto@secp256k1:address_to_string(Address)}
end
)
end
).
-file("src/gleeth/crypto/wallet.gleam", 183).
?DOC(" Verify that a specific address signed a personal message.\n").
-spec verify_personal_message(binary(), binary(), binary()) -> {ok, boolean()} |
{error, wallet_error()}.
verify_personal_message(Message, Signature_hex, Expected_address) ->
gleam@result:'try'(
recover_personal_message(Message, Signature_hex),
fun(Recovered) ->
{ok,
string:lowercase(Recovered) =:= string:lowercase(
Expected_address
)}
end
).
-file("src/gleeth/crypto/wallet.gleam", 208).
?DOC(" Convert a wallet to a summary string for display\n").
-spec to_string(wallet()) -> binary().
to_string(Wallet) ->
Address = get_address(Wallet),
Private_key = get_private_key_hex(Wallet),
<<<<<<<<<<<<<<"Wallet {\n"/utf8, " address: "/utf8>>/binary,
Address/binary>>/binary,
"\n"/utf8>>/binary,
" private_key: "/utf8>>/binary,
Private_key/binary>>/binary,
"\n"/utf8>>/binary,
"}"/utf8>>.
-file("src/gleeth/crypto/wallet.gleam", 223).
?DOC(" Convert a wallet to a compact summary for logging\n").
-spec to_summary(wallet()) -> binary().
to_summary(Wallet) ->
Address = get_address(Wallet),
<<<<"Wallet("/utf8, Address/binary>>/binary, ")"/utf8>>.
-file("src/gleeth/crypto/wallet.gleam", 229).
?DOC(" Check if two wallets are the same (have same private key)\n").
-spec equals(wallet(), wallet()) -> boolean().
equals(Wallet1, Wallet2) ->
get_private_key_hex(Wallet1) =:= get_private_key_hex(Wallet2).
-file("src/gleeth/crypto/wallet.gleam", 238).
?DOC(" Convert WalletError to string for display\n").
-spec error_to_string(wallet_error()) -> binary().
error_to_string(Error) ->
case Error of
{invalid_private_key, Msg} ->
<<"Invalid private key: "/utf8, Msg/binary>>;
{invalid_hex, Msg@1} ->
<<"Invalid hex format: "/utf8, Msg@1/binary>>;
{key_generation_failed, Msg@2} ->
<<"Key generation failed: "/utf8, Msg@2/binary>>;
{signing_failed, Msg@3} ->
<<"Signing failed: "/utf8, Msg@3/binary>>
end.
-file("src/gleeth/crypto/wallet.gleam", 248).
?DOC(" Check if an error is recoverable\n").
-spec is_recoverable_error(wallet_error()) -> boolean().
is_recoverable_error(Error) ->
case Error of
{invalid_private_key, _} ->
false;
{invalid_hex, _} ->
false;
{key_generation_failed, _} ->
true;
{signing_failed, _} ->
true
end.