Skip to content

Commit

Permalink
Add simple HD wallet example
Browse files Browse the repository at this point in the history
  • Loading branch information
bbrtj committed Oct 13, 2023
1 parent e93a91e commit 7f4ae27
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 1 deletion.
5 changes: 4 additions & 1 deletion ex/networks.pl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
=head1 Network usage example
This example is a step-by-step usage example of the network system from the perspective of an user (with networks already registered). Provided test cases explain how the network is propagated through an usual chain of element generation (extended key -> basic key, private key -> public key).
This example is a step-by-step usage example of the network system from the
perspective of an user (with networks already registered). Provided test cases
explain how the network is propagated through an usual chain of element
generation (extended key -> basic key, private key -> public key).
See inline comments for explanations of the test cases provided.
173 changes: 173 additions & 0 deletions ex/persistent_hd_wallet.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use v5.10;
use strict;
use warnings;

{

package Wallet;

use Moo;
use Mooish::AttributeBuilder;

use Bitcoin::Crypto qw(btc_extprv);
use Bitcoin::Crypto::Network;
use Storable;
use Try::Tiny;

use constant FILENAME => '.wallet.state';

# not stored in object to avoid being serialized
my %master_keys;

has option 'mnemonic_file' => (
writer => 1,
);

has option 'mnemonic_password' => (
writer => 1,
);

has param 'network' => (
writer => 1,
default => 'bitcoin',
);

has param 'account' => (
writer => 1,
default => 0,
);

has param 'last_key' => (
writer => 1,
default => 0,
);

sub inc
{
my ($self) = @_;
$self->set_last_key($self->last_key + 1);
}

sub inc_account
{
my ($self) = @_;
$self->set_account($self->account + 1);
}

sub master_key
{
my ($self) = @_;

return $master_keys{$self};
}

sub save
{
my ($self) = @_;
store $self, $self->FILENAME;
}

sub restore
{
my ($class) = @_;

my $self;
try {
$self = retrieve $class->FILENAME;
}
catch {
$self = $class->new;
};

$self->initialize;
return $self;
}

sub initialize
{
my ($self) = @_;

if (!$self->has_mnemonic_file) {
say 'No file containing mnemonic was specified.';
print 'Path to file: ';

my $path = readline STDIN;
chomp $path;
$self->set_mnemonic_file($path);
}

if (!$self->has_mnemonic_password) {
say 'No mnemonic password was specified.';
print 'Password (leave empty if none): ';

my $pass = readline STDIN;
chomp $pass;
$self->set_mnemonic_password($pass);
}

my $words = do {
open my $fh, '<', $self->mnemonic_file
or die "couldn't open " . $self->mnemonic_file . ": $!";

my $mnemonic = readline $fh;
chomp $mnemonic;
$mnemonic;
};

Bitcoin::Crypto::Network->get($self->network)->set_default;
$master_keys{$self} = btc_extprv->from_mnemonic($words, $self->mnemonic_password);
}

sub get_key
{
my ($self, $account, $index) = @_;

return $self->master_key->derive_key_bip44(
purpose => 84,
account => $account // $self->account,
index => $index // $self->last_key
)->get_basic_key;
}
}

my $wallet = Wallet->restore;
my $arg = shift;

if (!$arg || $arg eq 'last') {
say 'Getting last used address';
}
elsif ($arg eq 'new') {
say 'Getting new address';
$wallet->inc;
}
elsif ($arg eq 'new_account') {
say 'Getting new account';
$wallet->inc_account;
$wallet->set_last_key(0);
}
elsif ($arg eq 'switch') {
my $network = $wallet->network;
my @available = qw(bitcoin bitcoin_testnet);
my ($new) = grep { $_ ne $network } @available;
say "Switching network to $new";
$wallet->set_network($new);
$wallet->initialize;
}
else {
die 'invalid argument';
}

my $pkey = $wallet->get_key;
say '(account ' . $wallet->account . ', index ' . $wallet->last_key . ')';
say 'address: ' . $pkey->get_public_key->get_address;
say 'priv: ' . $pkey->to_wif;

$wallet->save;

__END__
=head1 Hierarchical Deterministic wallet with persistence example
This example shows how to implement a wallet which follows HD wallet rules. It
is quite incomplete, but provides a solid base for custom features.

0 comments on commit 7f4ae27

Please sign in to comment.