Skip to content

Commit

Permalink
add auth function
Browse files Browse the repository at this point in the history
  • Loading branch information
nihow666 committed Mar 9, 2024
1 parent a459a03 commit 846b0bd
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 40 deletions.
194 changes: 161 additions & 33 deletions backpack_exchange_sdk/authenticated.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,63 +10,191 @@ class AuthenticationClient:
private_key_obj: ed25519.Ed25519PrivateKey

def __init__(self):
self.is_debug_mode = False
self.network_proxies = {'http': '', 'https': ''}
self.key = ''
self.secret = ''

def setup(self, key, secret):
def setup(self, key, secret, window: int = 5000):
self.key = key
self.secret = secret
self.private_key_obj = ed25519.Ed25519PrivateKey.from_private_bytes(
base64.b64decode(secret)
)

def get_balances(self):
return self._send_request('GET', 'api/v1/capital', 'balanceQuery', {})

def get_deposits(self):
return self._send_request('GET', 'wapi/v1/capital/deposits', 'depositQueryAll', {})

def get_deposit_address(self, blockchain_name: str):
params = {'blockchain': blockchain_name}
return self._send_request('GET', 'wapi/v1/capital/deposit/address', 'depositAddressQuery', params)

def get_withdrawals(self, num: int, start: int):
params = {'limit': num, 'offset': start}
return self._send_request('GET', 'wapi/v1/capital/withdrawals', 'withdrawalQueryAll', params)

def make_withdrawal(self, wallet_address: str, asset_symbol: str, chain_name: str, amount: str):
data = {
'address': wallet_address,
'blockchain': chain_name,
'quantity': amount,
'symbol': asset_symbol,
}
return self._send_request('POST', 'wapi/v1/capital/withdrawals', 'withdraw', data)
self.window = window

def _send_request(self, method, endpoint, action, params):
url = f'{self.base_url}{endpoint}'
ts = int(time.time() * 1e3)
headers = self._generate_signature(action, ts, params)
if method == 'GET':
response = requests.get(url, headers=headers, params=params, proxies=self.network_proxies)
response = requests.get(url, headers=headers, params=params)
elif method == 'DELETE':
response = requests.delete(
url, headers=headers, data=json.dumps(params))
else:
response = requests.post(url, headers=headers, data=json.dumps(params), proxies=self.network_proxies)
print(json.dumps(params))
response = requests.post(
url, headers=headers, data=json.dumps(params))
if response.status_code != 200:
raise Exception(f"Error {response.status_code}: {response.text}")
return response.json()

def _generate_signature(self, action: str, timestamp: int, params=None):
if params is None:
params = {}
if 'postOnly' in params:
params = params.copy()
params['postOnly'] = str(params['postOnly']).lower()
param_str = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
sign_str = f"instruction={action}&{param_str}&timestamp={timestamp}&window={self.window}"
signature = base64.b64encode(self.private_key_obj.sign(sign_str.encode())).decode()
param_str = "&"+"&".join(f"{k}={v}" for k, v in sorted(params.items()))
if not param_str:
param_str = ''
sign_str = f"instruction={action}{param_str}&timestamp={timestamp}&window={self.window}"
print(sign_str)
signature = base64.b64encode(
self.private_key_obj.sign(sign_str.encode())).decode()
return {
"X-API-Key": self.key,
"X-Signature": signature,
"X-Timestamp": str(timestamp),
"X-Window": str(self.window),
"Content-Type": "application/json; charset=utf-8",
}

# ================================================================
# Capital - Capital management.
# ================================================================
def get_balances(self):
'''
Retrieves account balances and the state of the balances (locked or available).
Locked assets are those that are currently in an open order.
'''
return self._send_request('GET', 'api/v1/capital', 'balanceQuery', {})

def get_deposits(self, limit: int = 100, offset: int = 0):
"""
Retrieves deposit history.
"""
params = {'limit': limit, 'offset': offset}
return self._send_request('GET', 'wapi/v1/capital/deposits', 'depositQueryAll', params)

def get_deposit_address(self, blockchain_name: str):
"""
Retrieves the user specific deposit address if the user were to deposit on the specified blockchain.
"""
params = {'blockchain': blockchain_name}
return self._send_request('GET', 'wapi/v1/capital/deposit/address', 'depositAddressQuery', params)

def get_withdrawals(self, limit: int = 100, offset: int = 0):
"""
Retrieves withdrawal history.
"""
params = {'limit': limit, 'offset': offset}
return self._send_request('GET', 'wapi/v1/capital/withdrawals', 'withdrawalQueryAll', params)

def request_withdrawal(self, address: str, blockchain: str, quantity: str, symbol: str, clientId: str = None, twoFactorToken: str = None):
"""
The twoFactorToken field is required if the withdrawal address is not an address that is configured in the address book to not require 2FA.
The 2FA verification is currently experiencing errors. Please refrain from using 2FA for withdrawals at the moment.
"""
data = {
'address': address,
'blockchain': blockchain,
'quantity': quantity,
'symbol': symbol,
}
if clientId:
data['clientId'] = clientId
if twoFactorToken:
data['twoFactorToken'] = twoFactorToken
return self._send_request('POST', 'wapi/v1/capital/withdrawals', 'withdraw', data)

# ================================================================
# History - Historical account data.
# ================================================================
def get_order_history(self, symbol: str = None, limit: int = 100, offset: int = 0):
"""
Retrieves the order history for the user. This includes orders that have been filled and are no longer on the book. It may include orders that are on the book, but the /orders endpoint contains more up to date data.
"""
params = {'limit': limit, 'offset': offset}
if symbol:
params['symbol'] = symbol
return self._send_request('GET', 'wapi/v1/history/orders', 'orderHistoryQueryAll', params)

def get_fill_history(self, orderId: str = None, symbol: str = None, limit: int = 100, offset: int = 0):
"""
Retrieves historical fills, with optional filtering for a specific order or symbol.
"""
params = {'limit': limit, 'offset': offset}
if symbol:
params['symbol'] = symbol
if orderId:
params['orderId'] = orderId
return self._send_request('GET', 'wapi/v1/history/fills', 'fillHistoryQueryAll', params)
# ================================================================
# Order - Order management.
# ================================================================

def get_open_orders(self, symbol: str, clientId: int = -1, orderId: str = None):
"""
Retrieves an open order from the order book. This only returns the order if it is resting on the order book (i.e. has not been completely filled, expired, or cancelled).
"""
params = {'symbol': symbol}
if clientId > -1:
params['clientId'] = clientId
if orderId:
params['orderId'] = orderId
return self._send_request('GET', 'api/v1/order', 'orderQuery', params)

def execute_order(self, orderType: str, side: str, symbol: str, postOnly: bool = False, clientId: int = -1, price: str = None, quantity: str = None, timeInForce: str = None, quoteQuantity: str = None, selfTradePrevention: str = None, triggerPrice: str = None):
"""
Executes an order on the order book. If the order is not immediately filled, it will be placed on the order book.
Now only support Limit order.
"""
data = {
'orderType': orderType,
'symbol': symbol,
'side': side,
}
if orderType == 'Limit':
data['price'] = price
data['quantity'] = quantity
data['postOnly'] = postOnly
if timeInForce:
data['timeInForce'] = timeInForce
if orderType == 'Market':
data['quantity'] = quantity
if quoteQuantity:
data['quoteQuantity'] = quoteQuantity
if clientId > -1:
data['clientId'] = clientId
if selfTradePrevention:
data['selfTradePrevention'] = selfTradePrevention
if triggerPrice:
data['triggerPrice'] = triggerPrice
return self._send_request('POST', 'api/v1/order', 'orderExecute', data)

def cancel_open_order(self, symbol: str, clientId: int = -1, orderId: str = None):
"""
Cancels an open order from the order book.
One of orderId or clientId must be specified. If both are specified, then orderId takes precedence.
"""
data = {'symbol': symbol}
if clientId > -1:
data['clientId'] = clientId
if orderId:
data['orderId'] = orderId
return self._send_request('DELETE', 'api/v1/order', 'orderCancel', data)

def get_open_orders(self, symbol: str = None):
"""
Retrieves all open orders. If a symbol is provided, only open orders for that market will be returned, otherwise all open orders are returned.
"""
params = {}
if symbol:
params['symbol'] = symbol
return self._send_request('GET', 'api/v1/orders', 'orderQueryAll', params)

def cancel_open_orders(self, symbol: str):
"""
Cancels all open orders on the specified market.
"""
params = {'symbol': symbol}
return self._send_request('DELETE', 'api/v1/orders', 'orderCancelAll', params)
14 changes: 10 additions & 4 deletions backpack_exchange_sdk/public.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import requests


class PublicClient:
def __init__(self):
self.base_url = 'https://api.backpack.exchange/'


# ================================================================
# Market - Public market data.
# ================================================================
def get_assets(self):
"""
Retrieves all the assets that are supported by the exchange.
Expand All @@ -22,7 +25,7 @@ def get_ticker(self, symbol: str):
Retrieves summarised statistics for the last 24 hours for the given market symbol.
"""
return requests.get(url=f'{self.base_url}api/v1/ticker', params={'symbol': symbol}).json()

def get_tickers(self):
"""
Retrieves summarised statistics for the last 24 hours for all market symbols.
Expand All @@ -46,8 +49,9 @@ def get_klines(self, symbol: str, interval: str, start_time: int = 0, end_time:
params['endTime'] = end_time
return requests.get(url=f'{self.base_url}api/v1/klines', params=params).json()


# ================================================================
# System - Exchange system status.
# ================================================================
def get_status(self):
"""
Get the system status, and the status message, if any.
Expand All @@ -66,7 +70,9 @@ def get_system_time(self):
"""
return requests.get(url=f'{self.base_url}api/v1/time').text

# ================================================================
# Trades - Public trade data.
# ================================================================
def get_recent_trades(self, symbol: str, limit: int = 100):
"""
Retrieve the most recent trades for a symbol. This is public data and is not specific to any account.
Expand All @@ -80,4 +86,4 @@ def get_historical_trades(self, symbol: str, limit: int = 100, offset: int = 0):
Retrieves all historical trades for the given symbol. This is public trade data and is not specific to any account.
"""
params = {'symbol': symbol, 'limit': limit, 'offset': offset}
return requests.get(url=f'{self.base_url}api/v1/trades/history', params=params).json()
return requests.get(url=f'{self.base_url}api/v1/trades/history', params=params).json()
31 changes: 31 additions & 0 deletions examples/example_authenticated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from backpack_exchange_sdk.authenticated import AuthenticationClient

client = AuthenticationClient()
client.setup('key', 'secret')

# ================================================================
# Capital - Capital management.
# ================================================================
print(client.get_balances())
print(client.get_deposits())
print(client.get_deposit_address('Solana'))
print(client.get_withdrawals())
print(client.request_withdrawal('xxxxxxxxxx',
'Solana', '0.1', 'SOL', None, "999999"))

# ================================================================
# History - Historical account data.
# ================================================================
print(client.get_order_history('SOL_USDC'))
print(client.get_fill_history('SOL_USDC'))

# ================================================================
# Order - Order management.
# ================================================================
print(client.execute_order("Limit", "Ask", "SOL_USDC", True, 9999, "200", "0.1"))
print(client.execute_order("Limit", "Ask", "SOL_USDC", True, 9998, "200", "0.1"))
print(client.execute_order("Limit", "Ask", "SOL_USDC", True, 9997, "200", "0.1"))
print(client.get_open_orders('SOL_USDC', 9999))
print(client.cancel_open_orders('SOL_USDC', 9999))
print(client.get_open_orders('SOL_USDC'))
print(client.cancel_open_orders('SOL_USDC'))
2 changes: 1 addition & 1 deletion examples/example_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
print(public_client.get_recent_trades('SOL_USDC'))

# get historical trades
print(public_client.get_historical_trades('SOL_USDC',10,0))
print(public_client.get_historical_trades('SOL_USDC', 10, 0))
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="backpack_exchange_sdk",
version="0.0.1",
version="0.2.0",
author="solomeowl",
author_email="[email protected]",
description="A simple SDK for backpack exchange",
Expand All @@ -14,4 +14,4 @@
},

packages=find_packages(exclude=["tests"]),
)
)

0 comments on commit 846b0bd

Please sign in to comment.