File Coverage

blib/lib/Binance/API/Request.pm
Criterion Covered Total %
statement 94 116 81.0
branch 40 56 71.4
condition 20 24 83.3
subroutine 13 16 81.2
pod 4 4 100.0
total 171 216 79.1


line stmt bran cond sub pod time code
1             package Binance::API::Request;
2              
3             # MIT License
4             #
5             # Copyright (c) 2017 Lari Taskula
6             #
7             # Permission is hereby granted, free of charge, to any person obtaining a copy
8             # of this software and associated documentation files (the "Software"), to deal
9             # in the Software without restriction, including without limitation the rights
10             # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11             # copies of the Software, and to permit persons to whom the Software is
12             # furnished to do so, subject to the following conditions:
13             #
14             # The above copyright notice and this permission notice shall be included in all
15             # copies or substantial portions of the Software.
16             #
17             # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18             # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19             # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20             # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21             # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22             # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23             # SOFTWARE.
24              
25 3     3   388 use strict;
  3         5  
  3         73  
26 3     3   13 use warnings;
  3         5  
  3         83  
27              
28 3     3   13 use base 'LWP::UserAgent';
  3         7  
  3         1968  
29              
30 3     3   133168 use Digest::SHA qw( hmac_sha256_hex );
  3         8909  
  3         267  
31 3     3   1911 use JSON;
  3         26377  
  3         18  
32 3     3   2592 use Time::HiRes;
  3         3427  
  3         21  
33 3     3   245 use URI;
  3         5  
  3         78  
34 3     3   1537 use URI::QueryParam;
  3         1989  
  3         102  
35              
36 3     3   16 use Binance::Constants qw( :all );
  3         5  
  3         295  
37              
38 3     3   1228 use Binance::Exception::Parameter::Required;
  3         8  
  3         2846  
39              
40             =head1 NAME
41              
42             Binance::API::Request -- LWP::UserAgent wrapper for L
43              
44             =head1 DESCRIPTION
45              
46             This module provides a wrapper for LWP::UserAgent. Generates required parameters
47             for Binance API requests.
48              
49             =cut
50              
51             sub new {
52 5     5 1 10 my $class = shift;
53 5         20 my %params = @_;
54              
55 5         37 my $self = $class->SUPER::new;
56              
57 5         6034 $self->{apiKey} = $params{'apiKey'};
58 5         34 $self->{secretKey} = $params{'secretKey'};
59 5         37 $self->{recvWindow} = $params{'recvWindow'};
60 5         8 $self->{baseUrl} = $params{'baseUrl'};
61 5         10 $self->{logger} = $params{'logger'};
62              
63 5         20 bless $self, $class;
64             }
65              
66             sub get {
67 9     9 1 25 my ($self, $url, $params) = @_;
68              
69 9         34 my ($path, %data) = $self->_init($url, $params);
70 9         30 return $self->_exec('get', $path, %data);
71             }
72              
73             sub post {
74 0     0 1 0 my ($self, $url, $params) = @_;
75              
76 0         0 my ($path, %data) = $self->_init($url, $params);
77 0         0 return $self->_exec('post', $path, %data);
78             }
79              
80             sub delete {
81 0     0 1 0 my ($self, $url, $params) = @_;
82              
83 0         0 my ($path, %data) = $self->_init($url, $params);
84 0         0 return $self->_exec('delete', $path, %data);
85             }
86              
87             sub _exec {
88 0     0   0 my ($self, $method, $url, %data) = @_;
89              
90 0         0 $self->{logger}->debug("New request: $url");
91 0         0 $method = "SUPER::$method";
92 0         0 my $response;
93 0 0       0 if (keys %data > 0) {
94 0         0 $response = $self->$method($url, %data);
95             } else {
96 0         0 $response = $self->$method($url);
97             }
98 0 0       0 if ($response->is_success) {
99 0         0 $response = eval { decode_json($response->decoded_content); };
  0         0  
100 0 0       0 if ($@) {
101             $self->{logger}->error(
102 0 0       0 "Error decoding response. \nStatus => " . $response->code . ",\n"
103             . 'Content => ' . ($response->content ? $response->content : '')
104             );
105             }
106             } else {
107             $self->{logger}->error(
108 0 0       0 "Unsuccessful request. \nStatus => " . $response->code . ",\n"
109             . 'Content => ' . ($response->content ? $response->content : '')
110             );
111             }
112 0         0 return $response;
113             }
114              
115             sub _init {
116 18     18   3022 my ($self, $path, $params) = @_;
117              
118 18 50       49 unless ($path) {
119 0         0 Binance::Exception::Parameter::Required->throw(
120             error => 'Parameter "path" required',
121             parameters => ['path']
122             );
123             }
124              
125 18         29 my $timestamp = $params->{'timestamp'};
126 18         59 delete $params->{'timestamp'};
127             # Delete undefined query parameters
128 18         29 my $query = $params->{'query'};
129 18         51 foreach my $param (keys %$query) {
130 31 100       63 delete $query->{$param} unless defined $query->{$param};
131             }
132              
133             # Delete undefined body parameters
134 18         60 my $body = $params->{'body'};
135 18         49 foreach my $param (keys %$body) {
136 14 50       22 delete $body->{$param} unless defined $body->{$param};
137             }
138              
139 18         42 my $recvWindow;
140 18 100       55 if ($params->{signed}) {
141             $recvWindow = $query->{'recvWindow'} // $body->{'recvWindow'} //
142 6 50 100     31 defined $self->{'recvWindow'} ? $self->{'recvWindow'} : undef;
      66        
143             }
144              
145 18 100 66     98 $timestamp //= int Time::HiRes::time * 1000 if $params->{'signed'};
146              
147 18 50       113 my $base_url = defined $self->{'baseUrl'} ? $self->{'baseUrl'} : BASE_URL;
148 18         111 my $uri = URI->new( $base_url . $path );
149 18         16272 my $full_path = $uri->as_string;
150              
151 18         191 my %data;
152             # Mixed request (both query params & body params)
153 18 100 100     129 if (keys %$body && keys %$query) {
    100 66        
    50 100        
154 3 100 100     17 if (!defined $body->{'recvWindow'} && defined $recvWindow) {
    100 66        
155 1         3 $query->{'recvWindow'} = $recvWindow;
156             }
157             elsif (!defined $query->{'recvWindow'} && defined $recvWindow) {
158 1         3 $body->{'recvWindow'} = $recvWindow;
159             }
160              
161             # First, generate escaped parameter sets
162 3         7 my $tmp = $uri->clone;
163 3         23 $tmp->query_form($query);
164 3         254 my $query_params = $tmp->query();
165 3         28 $tmp->query_form($body);
166 3         204 my $body_params = $tmp->query();
167              
168             # Add timestamp to the end of body
169 3 100       27 $body_params .= "×tamp=$timestamp" if defined $timestamp;
170              
171             # Combine query and body parameters so that we can sign it.
172             # Binance documentation states that mixed content signature
173             # generation should not add the '&' character between query
174             # and body parameter sets.
175 3         7 my $to_sign = $query_params . $body_params;
176              
177 3         24 $self->{logger}->debug("Generating signature from: '$to_sign'");
178              
179             $body_params .= '&signature='.hmac_sha256_hex(
180             $to_sign, $self->{secretKey}
181 3 100       29 ) if $params->{signed};
182              
183 3         8 $full_path .= "?$query_params";
184 3         11 $data{'Content'} = $body_params;
185             }
186             # Query parameters only
187             elsif (keys %$query || !keys %$query && !keys %$body) {
188 12 100       28 $query->{'recvWindow'} = $recvWindow if $recvWindow;
189              
190 12         38 my $tmp = $uri->clone;
191 12         94 $tmp->query_form($query);
192 12         886 my $query_params = $tmp->query();
193              
194             # Add timestamp to the end of query
195 12 100       108 $query_params .= "×tamp=$timestamp" if defined $timestamp;
196              
197 12 100       96 $self->{logger}->debug("Generating signature from: '$query_params'")
198             if $query_params;
199              
200             $query_params .= '&signature='.hmac_sha256_hex(
201             $query_params, $self->{secretKey}
202 12 100       45 ) if $params->{signed};
203              
204 12 100       47 $full_path .= "?$query_params" if $query_params;
205             }
206             # Body parameters only
207             elsif (keys %$body) {
208 3 100       8 $body->{'recvWindow'} = $recvWindow if $recvWindow;
209              
210 3         5 $full_path = $uri->as_string;
211              
212 3         14 my $tmp = $uri->clone;
213 3         17 $tmp->query_form($body);
214 3         259 my $body_params = $tmp->query();
215              
216             # Add timestamp to the end of body
217 3 100       31 $body_params .= "×tamp=$timestamp" if defined $timestamp;
218              
219 3         20 $self->{logger}->debug("Generating signature from: '$body_params'");
220              
221             $body_params .= '&signature='.hmac_sha256_hex(
222             $body_params, $self->{secretKey}
223 3 100       22 ) if $params->{signed};
224              
225 3         7 $data{'Content'} = $body_params;
226             }
227              
228 18 50       41 if (defined $self->{apiKey}) {
229 0         0 $data{'X_MBX_APIKEY'} = $self->{apiKey};
230             }
231              
232 18         80 return ($full_path, %data);
233             }
234              
235             1;