Skip to content

Commit

Permalink
Merge pull request #107 from dvcrn/pass-api-key
Browse files Browse the repository at this point in the history
Add option for specifying apikey/secret through opts
  • Loading branch information
dvcrn authored Sep 2, 2024
2 parents f734c7a + 3254b38 commit fa9e9bd
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[
{
"request": {
"body": "",
"headers": {
"X-MBX-APIKEY": "dummy_api_key"
},
"method": "get",
"options": [],
"request_body": "",
"url": "https://api.binance.com/api/v3/openOrders?timestamp=1698985036203&signature=***"
},
"response": {
"binary": false,
"body": "{\"code\":-2014,\"msg\":\"API-key format invalid.\"}",
"headers": {
"Content-Type": "application/json;charset=UTF-8",
"Content-Length": "46",
"Connection": "keep-alive",
"Date": "Fri, 03 Nov 2023 04:17:16 GMT",
"Server": "nginx",
"x-mbx-uuid": "b2c45e59-9b6d-470a-9c78-962dbfa17d9d",
"x-mbx-used-weight": "80",
"x-mbx-used-weight-1m": "80",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains",
"X-Frame-Options": "SAMEORIGIN",
"X-Xss-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"Content-Security-Policy": "default-src 'self'",
"X-Content-Security-Policy": "default-src 'self'",
"X-WebKit-CSP": "default-src 'self'",
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
"Expires": "0",
"X-Cache": "Error from cloudfront",
"Via": "1.1 12632bbc89afe55228d7f1ab9e5993a6.cloudfront.net (CloudFront)",
"X-Amz-Cf-Pop": "NRT57-C3",
"X-Amz-Cf-Id": "vAUtPe6jUC-CzKpGocdbeFjnnILu7o6w9tNsBybIiHQwNO4xvETT_g=="
},
"status_code": 401,
"type": "ok"
}
}
]
21 changes: 19 additions & 2 deletions lib/binance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ docs
|> Enum.map(fn param ->
%{name: param.name, description: param.description}
end)
|> Kernel.++([
%{name: "api_key", description: "Binance API key, will overwrite env key"},
%{name: "secret_key", description: "Binance API secret, will overwrite env secret"}
])

optional_params =
case needs_timestamp do
Expand Down Expand Up @@ -154,7 +158,14 @@ docs
binding = binding()

# merge all passed args together, so opts + passed
all_passed_args = Keyword.merge(binding, opts) |> Keyword.drop([:opts])
all_passed_args =
Keyword.merge(binding, opts) |> Keyword.drop([:opts, :api_key, :secret_key])

api_key =
Keyword.get(opts, :api_key) || Application.get_env(:binance, :api_key, "")

secret_key =
Keyword.get(opts, :secret_key) || Application.get_env(:binance, :secret_key, "")

# if the call requires a timestamp, we add it
adjusted_args =
Expand Down Expand Up @@ -185,7 +196,13 @@ docs

case unquote(needs_auth) do
true ->
case HTTPClient.signed_request_binance(unquote(url), adjusted_args, unquote(method)) do
case HTTPClient.signed_request_binance(
unquote(url),
api_key,
secret_key,
adjusted_args,
unquote(method)
) do
{:ok, %{"code" => _code, "msg" => _msg} = err} ->
{:error, Binance.Helper.format_error(err)}

Expand Down
39 changes: 27 additions & 12 deletions lib/binance/rest/http_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ defmodule Binance.Rest.HTTPClient do
end
end

defp request_binance(url, body, method) do
defp request_binance(url, api_key, body, method) do
url = URI.parse("#{endpoint()}#{url}")

encoded_url =
Expand All @@ -48,15 +48,15 @@ defmodule Binance.Rest.HTTPClient do
HTTPoison.get(
URI.to_string(encoded_url),
[
{"X-MBX-APIKEY", Application.get_env(:binance, :api_key)}
{"X-MBX-APIKEY", api_key}
]
)

:delete ->
HTTPoison.delete(
URI.to_string(encoded_url),
[
{"X-MBX-APIKEY", Application.get_env(:binance, :api_key)}
{"X-MBX-APIKEY", api_key}
]
)

Expand All @@ -65,7 +65,7 @@ defmodule Binance.Rest.HTTPClient do
URI.to_string(encoded_url),
"",
[
{"X-MBX-APIKEY", Application.get_env(:binance, :api_key)}
{"X-MBX-APIKEY", api_key}
]
])
end
Expand All @@ -81,23 +81,29 @@ defmodule Binance.Rest.HTTPClient do
end
end

def signed_request_binance(url, params, method) do
argument_string =
params
|> prepare_query_params()
def signed_request_binance(url, api_key, secret_key, params, method) do
case validate_credentials(secret_key, api_key) do
:ok ->
argument_string =
params
|> prepare_query_params()

# generate signature
signature =
generate_signature(
:sha256,
Application.get_env(:binance, :secret_key),
secret_key,
argument_string
)
|> Base.encode16()

body = "#{argument_string}&signature=#{signature}"
body = "#{argument_string}&signature=#{signature}"

request_binance(url, body, method)
request_binance(url, api_key, body, method)

e ->
e
end
end

@doc """
Expand All @@ -110,18 +116,27 @@ defmodule Binance.Rest.HTTPClient do
data
|> prepare_query_params()

request_binance(url, argument_string, method)
request_binance(url, "", argument_string, method)
end

defp validate_credentials(nil, nil),
do: {:error, {:config_missing, "Secret and API key missing"}}

defp validate_credentials("", ""),
do: {:error, {:config_missing, "Secret and API key missing"}}

defp validate_credentials(nil, _api_key),
do: {:error, {:config_missing, "Secret key missing"}}

defp validate_credentials("", _api_key),
do: {:error, {:config_missing, "Secret key missing"}}

defp validate_credentials(_secret_key, nil),
do: {:error, {:config_missing, "API key missing"}}

defp validate_credentials(_secret_key, ""),
do: {:error, {:config_missing, "API key missing"}}

defp validate_credentials(_secret_key, _api_key),
do: :ok

Expand Down
44 changes: 44 additions & 0 deletions test/binance_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,38 @@ defmodule BinanceTest do
HTTPoison.start()
end

setup do
Application.put_env(:binance, :api_key, "api_key")
Application.put_env(:binance, :secret_key, "api_key")
end

test "ping returns an empty map" do
use_cassette "ping_ok" do
assert Binance.Market.get_ping() == {:ok, %{}}
end
end

test "return err when no key or secret" do
use_cassette "api_key_missing" do
Application.put_env(:binance, :api_key, "")
Application.put_env(:binance, :secret_key, "")

assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) ==
{:error, {:config_missing, "Secret and API key missing"}}

Application.put_env(:binance, :secret_key, "secret")

assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) ==
{:error, {:config_missing, "API key missing"}}

Application.put_env(:binance, :secret_key, "")
Application.put_env(:binance, :api_key, "apikey")

assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) ==
{:error, {:config_missing, "Secret key missing"}}
end
end

test "get_server_time success return an ok, time tuple" do
use_cassette "get_server_time_ok" do
assert Binance.Market.get_time() == {:ok, 1_616_592_268_319}
Expand All @@ -33,6 +59,24 @@ defmodule BinanceTest do
end
end

test "api_key and secret_key are correctly passed to request" do
existing_setting = ExVCR.Setting.get(:filter_request_headers)
ExVCR.Config.filter_request_headers(nil)

use_cassette "get_open_orders_for_api_key_and_secret_key",
match_requests_on: [:headers, :request_body] do
assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} =
Binance.Trade.get_open_orders(api_key: "dummy_api_key", secret_key: "hoge")

Application.put_env(:binance, :api_key, "dummy_api_key")

assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} =
Binance.Trade.get_open_orders()
end

ExVCR.Setting.set(:filter_request_headers, existing_setting)
end

test "get_exchange_info success returns the trading rules and symbol information" do
use_cassette "get_exchange_info_ok" do
assert {:ok, %Binance.Structs.ExchangeInfo{} = info} = Binance.Market.get_exchange_info()
Expand Down

0 comments on commit fa9e9bd

Please sign in to comment.