line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*-cperl-*- |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Business::Bitcoin::Request - Bitcoin payment request |
4
|
|
|
|
|
|
|
# Copyright (c) Ashish Gulhati |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# $Id: lib/Business/Bitcoin/Request.pm v1.051 Tue Oct 16 22:26:58 PDT 2018 $ |
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
61
|
|
9
|
2
|
|
|
2
|
|
9
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
47
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Business::Bitcoin::Request; |
12
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
8
|
use DBI; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
50
|
|
14
|
2
|
|
|
2
|
|
1082
|
use LWP::UserAgent; |
|
2
|
|
|
|
|
78409
|
|
|
2
|
|
|
|
|
58
|
|
15
|
2
|
|
|
2
|
|
12
|
use HTTP::Request; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
41
|
|
16
|
2
|
|
|
2
|
|
1092
|
use Math::EllipticCurve::Prime; |
|
2
|
|
|
|
|
92297
|
|
|
2
|
|
|
|
|
63
|
|
17
|
2
|
|
|
2
|
|
14
|
use Math::EllipticCurve::Prime::Point; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
61
|
|
18
|
2
|
|
|
2
|
|
874
|
use Digest::SHA qw(sha256 sha256_hex hmac_sha512_hex); |
|
2
|
|
|
|
|
4092
|
|
|
2
|
|
|
|
|
155
|
|
19
|
2
|
|
|
2
|
|
748
|
use Encode::Base58::BigInt; |
|
2
|
|
|
|
|
1755
|
|
|
2
|
|
|
|
|
93
|
|
20
|
2
|
|
|
2
|
|
809
|
use Crypt::RIPEMD160; |
|
2
|
|
|
|
|
4663
|
|
|
2
|
|
|
|
|
80
|
|
21
|
|
|
|
|
|
|
|
22
|
2
|
|
|
2
|
|
12
|
use vars qw( $VERSION $AUTOLOAD ); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3165
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
our ( $VERSION ) = '$Revision: 1.051 $' =~ /\s+([\d\.]+)/; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub new { |
27
|
2
|
|
|
2
|
1
|
10
|
my ($class, %args) = @_; |
28
|
2
|
50
|
33
|
|
|
16
|
return undef if $args{Amount} !~ /^\d+$/; return undef if $args{StartIndex} and $args{StartIndex} =~ /\D/; |
|
2
|
50
|
|
|
|
10
|
|
29
|
2
|
|
|
|
|
74
|
my $db = $args{_BizBTC}->db; my $xpub = $args{_BizBTC}->xpub; |
|
2
|
|
|
|
|
13
|
|
30
|
2
|
|
|
|
|
6
|
my $timestamp = time; |
31
|
2
|
100
|
|
|
|
6
|
my $index = defined $args{StartIndex} ? $args{StartIndex} : 'NULL'; |
32
|
2
|
100
|
|
|
|
7
|
my $refid = defined $args{Reference} ? "'$args{Reference}'" : 'NULL'; |
33
|
2
|
50
|
|
|
|
19
|
return undef unless $db->do("INSERT INTO requests values ($index, '$args{Amount}', NULL, $refid, '$timestamp', NULL, NULL);"); |
34
|
2
|
|
|
|
|
390
|
$index = $db->last_insert_id('%', '%', 'requests', 'reqid'); |
35
|
2
|
|
|
|
|
13
|
$ENV{PATH} = undef; |
36
|
2
|
50
|
|
|
|
7
|
return undef unless my $address = _getaddress($args{_BizBTC}, $index); |
37
|
2
|
|
|
|
|
48
|
my $rows = $db->do("UPDATE requests set address='$address' where reqid='$index';"); |
38
|
|
|
|
|
|
|
bless { Address => $address, |
39
|
|
|
|
|
|
|
ID => $index, |
40
|
|
|
|
|
|
|
Amount => $args{Amount}, |
41
|
|
|
|
|
|
|
DB => $db, |
42
|
|
|
|
|
|
|
Reference => $args{Reference}, |
43
|
2
|
50
|
|
|
|
726
|
Confirmations => defined $args{Confirmations} ? $args{Confirmations} : 5, |
44
|
|
|
|
|
|
|
Created => $timestamp }, $class; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub verify { |
48
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
49
|
0
|
|
|
|
|
0
|
my $ua = new LWP::UserAgent; |
50
|
0
|
|
|
|
|
0
|
my $req = HTTP::Request->new(GET => 'https://blockchain.info/q/addressbalance/' . $self->address . '?confirmations=' . $self->confirmations); |
51
|
0
|
|
|
|
|
0
|
my $res = $ua->request($req); |
52
|
0
|
|
|
|
|
0
|
my $paid = $res->content; |
53
|
0
|
0
|
|
|
|
0
|
$self->error($paid), return if $paid =~ /\D/; |
54
|
0
|
|
|
|
|
0
|
$self->error(''); |
55
|
0
|
0
|
|
|
|
0
|
$paid >= $self->amount ? $paid : 0; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub _find { |
59
|
2
|
|
|
2
|
|
24
|
my ($class, %args) = @_; |
60
|
2
|
50
|
66
|
|
|
15
|
return unless defined $args{Address} or defined $args{Reference}; |
61
|
2
|
50
|
66
|
|
|
12
|
return if defined $args{Address} and defined $args{Reference}; |
62
|
2
|
50
|
33
|
|
|
18
|
return unless defined $args{_BizBTC} and $args{_BizBTC}->db->ping; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
my $query = 'SELECT reqid,address,amount,reference,created,processed,status from requests WHERE ' . |
65
|
2
|
100
|
|
|
|
56
|
(defined $args{Address} ? "address='$args{Address}';" : "reference='$args{Reference}';"); |
66
|
2
|
|
|
|
|
12
|
my ($reqid, $address, $amount, $refid, $created, $processed, $status) = $args{_BizBTC}->db->selectrow_array($query); |
67
|
|
|
|
|
|
|
bless { Address => $address, |
68
|
|
|
|
|
|
|
Amount => $amount, |
69
|
|
|
|
|
|
|
Reference => $refid, |
70
|
|
|
|
|
|
|
ID => $reqid, |
71
|
|
|
|
|
|
|
DB => $args{_BizBTC}->db,, |
72
|
2
|
50
|
|
|
|
347
|
Confirmations => defined $args{Confirmations} ? $args{Confirmations} : 5, |
73
|
|
|
|
|
|
|
Created => $created, |
74
|
|
|
|
|
|
|
Processed => $processed, |
75
|
|
|
|
|
|
|
Status => $status |
76
|
|
|
|
|
|
|
}, $class; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub commit { |
80
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
81
|
0
|
|
|
|
|
0
|
my $processed = $self->processed; |
82
|
0
|
|
|
|
|
0
|
my $status = $self->status; |
83
|
0
|
|
|
|
|
0
|
my $amount = $self->amount; |
84
|
0
|
|
|
|
|
0
|
my $refid = $self->reference; |
85
|
0
|
|
|
|
|
0
|
my $address = $self->address; |
86
|
0
|
|
|
|
|
0
|
my @updates; |
87
|
0
|
0
|
|
|
|
0
|
push @updates, "processed = '" . $processed . "'" if $processed; |
88
|
0
|
0
|
|
|
|
0
|
push @updates, "reference = '" . $refid . "'" if $refid; |
89
|
0
|
0
|
|
|
|
0
|
push @updates, "status = '" . $status . "'" if $status; |
90
|
0
|
0
|
|
|
|
0
|
push @updates, "amount = '" . $amount . "'" if $amount; |
91
|
0
|
0
|
|
|
|
0
|
return 1 unless my $updates = join ',',@updates; |
92
|
0
|
0
|
|
|
|
0
|
return undef unless $self->db->do("UPDATE requests SET $updates where address='$address';"); |
93
|
0
|
|
|
|
|
0
|
return 1; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub _getaddress { |
97
|
2
|
|
|
2
|
|
8
|
my ($bizbtc, $index) = @_; |
98
|
2
|
|
|
|
|
13
|
my $xpub = $bizbtc->xpub; |
99
|
2
|
|
|
|
|
17
|
my $curve = Math::EllipticCurve::Prime->from_name('secp256k1'); |
100
|
2
|
|
|
|
|
8153
|
my $xpubdata = Math::BigInt->new(_decode58($xpub))->as_hex; |
101
|
2
|
|
|
|
|
87776
|
$xpubdata =~ /.(.{8})(..)(.{8})(.{8})(.{64})(.{66})(.*)/; |
102
|
2
|
|
|
|
|
18
|
my ($ver, $depth, $fp, $i, $c, $Kc) = ($1, $2, $3, $4, $5, $6); |
103
|
2
|
|
|
|
|
10
|
my $K = Math::EllipticCurve::Prime::Point->from_hex(_decompress($Kc)); |
104
|
2
|
100
|
|
|
|
3962
|
if ($bizbtc->path eq 'electrum') { |
105
|
|
|
|
|
|
|
# m/0 |
106
|
1
|
|
|
|
|
7
|
my ($Ki, $ci) = _CKDpub($K, $c, 0); |
107
|
|
|
|
|
|
|
# m/0/$index |
108
|
1
|
|
|
|
|
7
|
my ($Ki2, $ci2) = _CKDpub($Ki, $ci, $index); |
109
|
1
|
|
|
|
|
5
|
return _address(_compress($Ki2)); |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
else { |
112
|
1
|
|
|
|
|
5
|
my ($Ki, $ci) = _CKDpub($K, $c, $index); |
113
|
1
|
|
|
|
|
6
|
return _address(_compress($Ki)); |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub _CKDpub { |
118
|
3
|
|
|
3
|
|
11
|
my ($K, $c, $i) = @_; |
119
|
3
|
|
|
|
|
21
|
my $curve = Math::EllipticCurve::Prime->from_name('secp256k1'); |
120
|
3
|
|
|
|
|
12233
|
my $data = pack('H*', _compress($K)) . pack ('L>', $i); |
121
|
3
|
|
|
|
|
706
|
my $hmac = hmac_sha512_hex($data, pack('H*', $c)); $hmac =~ /(.{64})(.{64})/; |
|
3
|
|
|
|
|
14
|
|
122
|
3
|
|
|
|
|
13
|
my ($Il, $ci) = ($1, $2); |
123
|
3
|
|
|
|
|
16
|
my $Ki = $curve->g->multiply(Math::BigInt->from_hex($Il))->add($K); |
124
|
3
|
|
|
|
|
37801926
|
return ($Ki, $ci); |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub _address { |
128
|
2
|
|
|
2
|
|
507
|
my $sha256 = sha256(pack('H*', shift)); |
129
|
2
|
|
|
|
|
20
|
my $id = '00' . Crypt::RIPEMD160->hexhash($sha256); $id =~ s/\s//g; |
|
2
|
|
|
|
|
142
|
|
130
|
2
|
|
|
|
|
24
|
my $checksum = substr(sha256_hex(sha256(pack('H*', $id))), 0, 8); |
131
|
2
|
|
|
|
|
17
|
my $address = _encode58(Math::BigInt->from_hex($id . $checksum)); |
132
|
2
|
|
|
|
|
10
|
my $leadingones; |
133
|
2
|
|
|
|
|
24
|
while ($id =~ /^(00)/) { $leadingones .= '1'; $id =~ s/^00//; } |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
17
|
|
134
|
2
|
|
|
|
|
51
|
return $leadingones . $address; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub _decompress { |
138
|
2
|
|
|
2
|
|
4
|
my $Kc = shift; $Kc =~ /^(..)(.*)/; |
|
2
|
|
|
|
|
7
|
|
139
|
2
|
|
|
|
|
7
|
my $i = $1; my $K = '04' . '0' x (64 - length($2)) . $2; my $x = Math::BigInt->from_hex($2); |
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
9
|
|
140
|
2
|
|
|
|
|
1774
|
my $curve = Math::EllipticCurve::Prime->from_name('secp256k1'); |
141
|
2
|
|
|
|
|
7855
|
my ($p, $a, $b) = ($curve->p, $curve->a, $curve->b); |
142
|
2
|
|
|
|
|
35
|
my $y = ($x->bmodpow(3,$p)+$a*$x+$b)->bmodpow(($p+1)/4,$p); |
143
|
2
|
50
|
|
|
|
792324
|
$y = $p - $y if $i%2 ne $y%2; |
144
|
2
|
|
|
|
|
951
|
my $yhex = $y->as_hex; $yhex =~ s/^0x//; |
|
2
|
|
|
|
|
1945
|
|
145
|
2
|
|
|
|
|
10
|
$K .= '0' x (64 - length($yhex)) . $yhex; |
146
|
2
|
|
|
|
|
21
|
return $K; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub _compress { |
150
|
5
|
|
|
5
|
|
13
|
my $K = shift; |
151
|
5
|
|
|
|
|
22
|
my $Kc = $K->x->as_hex; $Kc =~ s/^0x//; |
|
5
|
|
|
|
|
5016
|
|
152
|
5
|
|
|
|
|
20
|
$Kc = '0' x (64 - length($Kc)) . $Kc; |
153
|
5
|
100
|
|
|
|
22
|
$Kc = ($K->y % 2 ? '03' : '02') . $Kc; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub _decode58 { |
157
|
2
|
|
|
2
|
|
4
|
my $todecode = shift; |
158
|
2
|
|
|
|
|
6
|
$todecode =~ tr/A-HJ-NP-Za-km-z/a-km-zA-HJ-NP-Z/; |
159
|
2
|
|
|
|
|
11
|
my $decoded = decode_base58($todecode); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub _encode58 { |
163
|
2
|
|
|
2
|
|
1356
|
my $encoded = encode_base58(shift); |
164
|
2
|
|
|
|
|
20075
|
$encoded =~ tr/a-km-zA-HJ-NP-Z/A-HJ-NP-Za-km-z/; |
165
|
2
|
|
|
|
|
7
|
return $encoded; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub AUTOLOAD { |
169
|
12
|
|
|
12
|
|
1180
|
my $self = shift; (my $auto = $AUTOLOAD) =~ s/.*:://; |
|
12
|
|
|
|
|
58
|
|
170
|
12
|
100
|
|
|
|
137
|
return if $auto eq 'DESTROY'; |
171
|
8
|
50
|
66
|
|
|
40
|
if ($auto =~ /^(confirmations|processed|status|amount|reference|error)$/x and defined $_[0]) { |
172
|
0
|
|
|
|
|
0
|
$self->{"\u$auto"} = shift; |
173
|
|
|
|
|
|
|
} |
174
|
8
|
50
|
|
|
|
60
|
if ($auto =~ /^(reqid|amount|address|reference|version|created|confirmations|processed|status|error)$/x) { |
175
|
8
|
|
|
|
|
56
|
return $self->{"\u$auto"}; |
176
|
|
|
|
|
|
|
} |
177
|
0
|
0
|
|
|
|
|
if ($auto =~ /^(db|id)$/x) { |
178
|
0
|
|
|
|
|
|
return $self->{"\U$auto"}; |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
|
die "Could not AUTOLOAD method $auto."; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
1; # End of Business::Bitcoin::Request |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=head1 NAME |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Business::Bitcoin::Request - Bitcoin payment request |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head1 VERSION |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
$Revision: 1.051 $ |
192
|
|
|
|
|
|
|
$Date: Tue Oct 16 22:26:58 PDT 2018 $ |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 SYNOPSIS |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Business::Bitcoin::Request objects represent Bitcoin payment requests |
197
|
|
|
|
|
|
|
generated by Business::Bitcoin. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
use Business::Bitcoin; |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
my $bizbtc = new Business::Bitcoin (DB => '/tmp/bizbtc.db', |
202
|
|
|
|
|
|
|
XPUB => 'xpub...'); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $request = $bizbtc->request(Amount => 4200); |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
print ($request->verify ? "Verified\n" : "Verification failed\n"); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head1 METHODS |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head2 new |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Not intended to be called directly. Business::Bitcoin::Request objects |
213
|
|
|
|
|
|
|
should be created by calling the request method on a Business::Bitcoin |
214
|
|
|
|
|
|
|
object. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head2 commit |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Commit the Request object to the requests database. Only the |
219
|
|
|
|
|
|
|
'processed' and 'status' fields are updated in the database. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 verify |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
Verify that the request has been paid. Returns the total unspent |
224
|
|
|
|
|
|
|
balance at the address corresponding to the request if the request has |
225
|
|
|
|
|
|
|
been paid, and 0 if the balance at the address is lower than the |
226
|
|
|
|
|
|
|
request amount. The number of confirmations required to consider a |
227
|
|
|
|
|
|
|
payment valid can be set via the confirmations accessor. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head1 ACCESSORS |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Accessors can be called with no arguments to query the value of an |
232
|
|
|
|
|
|
|
object property, or with a single argument, to set the property to a |
233
|
|
|
|
|
|
|
specific value (unless the property is read only). |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=head2 confirmations |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
The number of confirmations needed to consider a payment valid. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head2 amount |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
The amount of the payment request, in Satoshi. Read only. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head2 address |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
The Bitcoin receiving address for the payment request. Read only. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=head2 created |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
The timestamp of when the request was created. Stored as an int in the |
250
|
|
|
|
|
|
|
requests database. Read only. |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head2 reference |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
An optional reference ID for the request, to facilitate integration |
255
|
|
|
|
|
|
|
with existing order systems. Stored as a text field in the requests |
256
|
|
|
|
|
|
|
database. Read only. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=head2 processed |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
An optional property for applications to record the timestamp of when |
261
|
|
|
|
|
|
|
the transaction was processed. Stored as an int in the requests |
262
|
|
|
|
|
|
|
database. Read/write. |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=head2 status |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
An optional property that can be used to record the status of the |
267
|
|
|
|
|
|
|
transaction ('processed', 'shipped', 'refunded', etc.). Stored as a |
268
|
|
|
|
|
|
|
text field in the requests database. Read/write. |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=head2 error |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
If the last verify() returned undef, this accessor will return the |
273
|
|
|
|
|
|
|
error string that was received from the blockchain API call. |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=head2 version |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
The version number of this module. Read only. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=head1 AUTHOR |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
Ashish Gulhati, C<< >> |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head1 BUGS |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
286
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
287
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=head1 SUPPORT |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
perldoc Business::Bitcoin::Request |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
You can also look for information at: |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=over 4 |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
L |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
L |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=item * CPAN Ratings |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
L |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=item * Search CPAN |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
L |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=back |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
Copyright (c) Ashish Gulhati. |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
This software package is Open Software; you can use, redistribute, |
322
|
|
|
|
|
|
|
and/or modify it under the terms of the Open Artistic License 2.0. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Please see L for the full license |
325
|
|
|
|
|
|
|
terms, and ensure that the license grant applies to you before using |
326
|
|
|
|
|
|
|
or modifying this software. By using or modifying this software, you |
327
|
|
|
|
|
|
|
indicate your agreement with the license terms. |