Files
beast-trader/dashboard/venv/lib/python3.12/site-packages/ccxt/pacifica.py

3099 lines
129 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
from ccxt.base.exchange import Exchange
from ccxt.abstract.pacifica import ImplicitAPI
import math
from ccxt.base.types import Any, Balances, Currency, Int, LedgerEntry, Leverage, MarginMode, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class pacifica(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(pacifica, self).describe(), {
'id': 'pacifica',
'name': 'Pacifica',
'countries': [],
'version': 'v1',
'isSandboxModeEnabled': False, # is testnet api
'rateLimit': 50, # 125 requests per minute without api-key(300 with api-key) ~ 2 req/sec = 1 req/500 ms.
'certified': False,
'pro': True,
'dex': True,
'has': {
'CORS': None,
'spot': False,
'margin': False,
'swap': True,
'future': True,
'option': False,
'addMargin': False,
'borrowCrossMargin': False,
'borrowIsolatedMargin': False,
'cancelAllOrders': True,
'cancelAllOrdersAfter': False,
'cancelOrder': True,
'cancelOrders': True,
'cancelOrdersForSymbols': None,
'closeAllPositions': False,
'closePosition': False,
'createMarketBuyOrderWithCost': False,
'createMarketOrderWithCost': False,
'createMarketSellOrderWithCost': False,
'createOrder': True,
'createOrders': True,
'createOrderWithTakeProfitAndStopLoss': True,
'createReduceOnlyOrder': True,
'createStopOrder': True,
'editOrder': True,
'editOrders': False,
'fetchAccounts': True,
'fetchBalance': True,
'fetchBorrowInterest': False,
'fetchBorrowRateHistories': False,
'fetchBorrowRateHistory': False,
'fetchCanceledAndClosedOrders': True,
'fetchCanceledOrders': True,
'fetchClosedOrders': True,
'fetchCrossBorrowRate': False,
'fetchCrossBorrowRates': False,
'fetchCurrencies': False,
'fetchDepositAddress': False,
'fetchDepositAddresses': False,
'fetchDeposits': False,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': False,
'fetchFundingHistory': True,
'fetchFundingRate': False,
'fetchFundingRateHistory': True,
'fetchFundingRates': True,
'fetchIndexOHLCV': False,
'fetchIsolatedBorrowRate': False,
'fetchIsolatedBorrowRates': False,
'fetchLedger': True,
'fetchLeverage': True,
'fetchLeverageTiers': False,
'fetchLiquidations': False,
'fetchMarginMode': True,
'fetchMarketLeverageTiers': False,
'fetchMarkets': True,
'fetchMarkOHLCV': False,
'fetchMyLiquidations': False,
'fetchMyTrades': True,
'fetchOHLCV': True,
'fetchOpenInterest': True,
'fetchOpenInterestHistory': False,
'fetchOpenInterests': True,
'fetchOpenOrders': True,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchOrders': True,
'fetchOrderTrades': False,
'fetchPosition': True,
'fetchPositionMode': False,
'fetchPositions': True,
'fetchPositionsRisk': False,
'fetchPremiumIndexOHLCV': False,
'fetchStatus': None,
'fetchTicker': 'emulated',
'fetchTickers': True,
'fetchTime': None,
'fetchTrades': True,
'fetchTradingFee': True,
'fetchTradingFees': False,
'fetchTransfer': False,
'fetchTransfers': False,
'fetchWithdrawal': False,
'fetchWithdrawals': False,
'reduceMargin': False,
'repayCrossMargin': False,
'repayIsolatedMargin': False,
'sandbox': True,
'setLeverage': True,
'setMarginMode': True,
'setPositionMode': False,
'transfer': True,
'withdraw': True,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'8h': '8h',
'12h': '12h',
'1d': '1d',
},
'hostname': 'pacifica.fi',
'urls': {
'logo': 'https://github.com/user-attachments/assets/f795515a-828e-4a04-8fca-bf19fcf17ea4',
'api': {
'public': 'https://api.{hostname}',
'private': 'https://api.{hostname}',
},
'test': {
'public': 'https://test-api.{hostname}',
'private': 'https://test-api.{hostname}',
},
'www': 'https://www.pacifica.fi',
'doc': 'https://docs.pacifica.fi/api-documentation/api/rest-api',
'fees': 'https://docs.pacifica.fi/trading-on-pacifica/trading-fees',
'referral': 'https://app.pacifica.fi?referral=ccxt',
},
'api': {
'public': {
'get': {
# ~12 weight depends on the limit 3 max for api-key, but min without api-key
'info': 1,
'info/prices': 1,
'kline': 12,
'kline/mark': 12,
'book': 1,
'trades': 1, # Recent
'funding_rate/history': 1,
'account': 1,
'account/settings': 1,
'positions': 1,
'trades/history': 12,
'funding/history': 1,
'portfolio': 1,
'account/balance/history': 12,
'orders': 1,
'orders/history': 12,
'orders/history_by_id': 1,
'account/builder_codes/approvals': 1,
},
},
'private': {
'post': {
'account/leverage': 1,
'account/margin': 1,
'account/withdraw': 1,
'account/subaccount/create': 1,
'account/subaccount/list': 1,
'account/subaccount/transfer': 1,
'orders/create': 1,
'orders/create_market': 1,
'orders/stop/create': 1,
'positions/tpsl': 1,
'orders/cancel': 0.5,
'orders/cancel_all': 0.5,
'orders/stop/cancel': 0.5,
'orders/edit': 1,
'orders/batch': 1,
'account/builder_codes/approve': 1,
'account/builder_codes/revoke': 1,
'agent/bind': 1,
'account/api_keys/create': 1,
'account/api_keys/revoke': 1,
'account/api_keys': 1,
},
},
},
'fees': {
'swap': {
'taker': self.parse_number('0.0004'),
'maker': self.parse_number('0.00015'),
},
},
#
# Reminder:
# If you're using an agent wallet, its private key must also be in the privateKey field in requiredCredentials.
# However, walletAddress must ALWAYS be equal to the main address. For the agent wallet address, there's a field in options: agentAddress
#
'requiredCredentials': {
'apiKey': False,
'secret': False,
'walletAddress': False, # agentAddress, apiKey only in options.
'privateKey': True, # base58 solana private key
},
'exceptions': {
'exact': {
'400': BadRequest,
'403': PermissionDenied,
'404': BadRequest,
'409': ExchangeError,
'422': ExchangeError,
'429': RateLimitExceeded,
'500': ExchangeError,
'503': ExchangeNotAvailable,
'504': RequestTimeout,
},
'broad': {
'UNKNOWN': ExchangeError,
'ACCOUNT_NOT_FOUND': ExchangeError,
'BOOK_NOT_FOUND': ExchangeError,
'INVALID_TICK_LEVEL': InvalidOrder,
'INSUFFICIENT_BALANCE': InsufficientFunds,
'ORDER_NOT_FOUND': OrderNotFound,
'OVER_WITHDRAWAL': InsufficientFunds,
'INVALID_LEVERAGE': ExchangeError,
'CANNOT_UPDATE_MARGIN': ExchangeError,
'POSITION_NOT_FOUND': ExchangeError,
'POSITION_TPSL_LIMIT_EXCEEDED': InvalidOrder,
},
},
'precisionMode': TICK_SIZE,
'commonCurrencies': {},
'options': {
'agentAddress': None,
'apiKey': None,
'builderCode': 'CCXT', # case sensitive
'feeRate': '0.01', # default rate for builder fee approval 0.01%
'builderFee': True,
'batchOrdersMax': 10,
'defaultType': 'swap',
'defaultSlippage': '0.5',
'expiryWindow': 5000,
'maxCostHugeWithApiKey': 3,
'marketHelperProps': [],
'defaultMarginMode': 'cross',
'builderSupportOperations': {
'create_market_order': True,
'create_limit_order': True,
'create_stop_order': True,
'set_position_tpsl': True,
},
},
'features': {
'default': {
'sandbox': True,
'createOrder': {
'marginMode': False,
'triggerPrice': False,
'triggerPriceType': None,
'triggerDirection': False,
'stopLossPrice': False,
'takeProfitPrice': False,
'attachedStopLossTakeProfit': {
'triggerPriceType': {
'last': False,
'mark': False,
'index': False,
},
'triggerPrice': True,
'type': True,
'price': True,
},
'timeInForce': {
'IOC': True,
'FOK': False,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'leverage': False,
'marketBuyByCost': False,
'marketBuyRequiresPrice': False,
'selfTradePrevention': False,
'iceberg': False,
},
'createOrders': {
'max': 10,
},
'editOrder': {
'side': False,
'type': False,
},
'fetchMyTrades': {
'marginMode': False,
'limit': 100,
'daysBack': None,
'untilDays': None,
'symbolRequired': False,
},
'fetchOrder': {
'marginMode': False,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
'fetchOpenOrders': {
'marginMode': False,
'limit': 100,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
'fetchOrders': {
'marginMode': False,
'limit': 100,
'daysBack': None,
'untilDays': None,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
'fetchClosedOrders': {
'marginMode': False,
'limit': 100,
'daysBack': None,
'daysBackCanceled': None,
'untilDays': None,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
'fetchOHLCV': {
'limit': 3950,
},
'fetchLedger': {
'code': False,
},
},
'forPerps': {
'extends': 'default',
'createOrder': {
'stopLossPrice': True,
'takeProfitPrice': True,
'attachedStopLossTakeProfit': None,
},
},
'spot': None,
'swap': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
'future': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
},
})
def initialize_client(self):
try:
self.handle_builder_fee_approval()
except Exception as e:
return False
return True
def handle_builder_fee_approval(self):
if self.isSandboxModeEnabled: # At self stage, building codes are mostly only on the mainnet.
return False
buildFee = self.safe_bool(self.options, 'builderFee', True)
if not buildFee:
return False # skip if builder fee is not enabled
approvedBuilderFee = self.safe_bool(self.options, 'approvedBuilderFee', False)
if approvedBuilderFee:
return True # skip if builder fee is already approved
try:
builder = self.safe_string(self.options, 'builderCode', 'CCXT') # case sensitive
maxFeeRate = self.safe_string(self.options, 'feeRate', '0.01')
self.approve_builder_code(builder, maxFeeRate)
self.options['approvedBuilderFee'] = True
except Exception as e:
self.options['builderFee'] = False # disable builder fee if an error occurs
return True
def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for pacifica
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
if self.check_required_credentials(False):
self.initialize_client()
self.load_account_settings()
swapMarkets = self.fetch_swap_markets(params)
return swapMarkets
def fetch_swap_markets(self, params={}) -> List[Market]:
"""
retrieves data on all swap markets for pacifica
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-market-info
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
response = self.publicGetInfo(params) # meta
# {
# "success": True,
# "data": [
# {
# "symbol": "ETH",
# "tick_size": "0.1",
# "min_tick": "0",
# "max_tick": "1000000",
# "lot_size": "0.0001",
# "max_leverage": 50,
# "isolated_only": False,
# "min_order_size": "10",
# "max_order_size": "5000000",
# "funding_rate": "0.0000125",
# "next_funding_rate": "0.0000125",
# "created_at": 1748881333944
# },
# {
# "symbol": "BTC",
# "tick_size": "1",
# "min_tick": "0",
# "max_tick": "1000000",
# "lot_size": "0.00001",
# "max_leverage": 50,
# "isolated_only": False,
# "min_order_size": "10",
# "max_order_size": "5000000",
# "funding_rate": "0.0000125",
# "next_funding_rate": "0.0000125",
# "created_at": 1748881333944
# },
# ....
# ],
# "error": null,
# "code": null
# }
meta = self.safe_list(response, 'data', [])
results = []
for i in range(0, len(meta)):
results.append(meta[i])
return self.parse_markets(results)
def parse_market(self, market: dict) -> Market:
# {
# "symbol": "ETH",
# "tick_size": "0.1",
# "min_tick": "0",
# "max_tick": "1000000",
# "lot_size": "0.0001",
# "max_leverage": 50,
# "isolated_only": False,
# "min_order_size": "10",
# "max_order_size": "5000000",
# "funding_rate": "0.0000125",
# "next_funding_rate": "0.0000125",
# "created_at": 1748881333944
# },
# {
# "symbol": "BTC",
# "tick_size": "1",
# "min_tick": "0",
# "max_tick": "1000000",
# "lot_size": "0.00001",
# "max_leverage": 50,
# "isolated_only": False,
# "min_order_size": "10",
# "max_order_size": "5000000",
# "funding_rate": "0.0000125",
# "next_funding_rate": "0.0000125",
# "created_at": 1748881333944
# },
quoteId = 'usdc'
settleId = 'usdc'
id = self.safe_string(market, 'symbol')
baseId = id.lower()
baseName = id.upper()
base = self.safe_currency_code(baseName)
quote = self.safe_currency_code(quoteId)
settle = self.safe_currency_code(settleId)
symbol = base + '/' + quote
contract = True
swap = True
if contract:
if swap:
symbol = symbol + ':' + settle
fees = self.safe_dict(self.fees, 'swap', {})
taker = self.safe_number(fees, 'taker')
maker = self.safe_number(fees, 'maker')
amountPrecisionStr = self.safe_string(market, 'lot_size')
pricePrecisionStr = self.safe_string(market, 'tick_size')
active = True # there is no non-active markets comes from endpoint market info
return self.safe_market_structure({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'baseName': baseName,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': False,
'margin': None,
'swap': swap,
'future': False,
'option': False,
'active': active,
'contract': contract,
'linear': True,
'inverse': False,
'taker': taker,
'maker': maker,
'contractSize': self.parse_number('1'),
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.parse_number(amountPrecisionStr),
'price': self.parse_number(pricePrecisionStr),
},
'limits': {
'leverage': {
'min': 1,
'max': self.safe_integer(market, 'max_leverage'),
},
'amount': {
'min': None,
'max': None,
},
'price': {
'min': self.safe_string(market, 'min_tick'),
'max': self.safe_string(market, 'max_tick'),
},
'cost': {
'min': None,
'max': None,
},
},
'created': None,
'marginModes': {'cross': True, 'isolated': True},
'info': market,
})
def fetch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-info
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: a `balance structure <https://docs.ccxt.com/?id=balance-structure>`
"""
userAccount = None
userAccount, params = self.handle_origin_and_single_address('fetchBalance', params)
request = {
'account': userAccount,
}
response = self.publicGetAccount(self.extend(request, params))
# {
# "success": True,
# "data": {
# "balance": "2000.000000",
# "fee_level": 0,
# "maker_fee": "0.00015",
# "taker_fee": "0.0004",
# "account_equity": "2150.250000",
# "available_to_spend": "1800.750000",
# "available_to_withdraw": "1500.850000",
# "pending_balance": "0.000000",
# "total_margin_used": "349.500000",
# "cross_mmr": "420.690000",
# "positions_count": 2,
# "orders_count": 3,
# "stop_orders_count": 1,
# "updated_at": 1716200000000,
# "use_ltp_for_stop_orders": False
# },
# "error": null,
# "code": null
# }
data = self.safe_dict(response, 'data', {})
result = {
'info': data,
}
result['free'] = {}
result['used'] = {}
result['total'] = {}
totalBalance = self.safe_number(data, 'account_equity')
usedMargin = self.safe_number(data, 'total_margin_used')
freeBalance = self.safe_number(data, 'available_to_spend')
result['total']['USDC'] = totalBalance
result['used']['USDC'] = usedMargin
result['free']['USDC'] = freeBalance
timestamp = self.safe_integer(data, 'updated_at')
result['timestamp'] = timestamp
result['datetime'] = self.iso8601(timestamp)
return self.safe_balance(result)
def fetch_leverage(self, symbol: str, params={}) -> Leverage:
"""
fetch the set leverage for a market
:param str symbol: unified symbol of the market
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: a `leverage structure <https://docs.ccxt.com/?id=leverage-structure>`
"""
self.load_account_settings()
self.load_markets()
market = self.market(symbol)
userAccount = None
userAccount, params = self.handle_origin_and_single_address('fetchLeverage', params)
cacheAddress = self.walletAddress
settings = None
if userAccount == cacheAddress:
settings = self.handle_option('fetchLeverage', 'settings', None)
else:
request: dict = {
'account': userAccount,
}
settings = self.fetch_account_settings(self.extend(request, params))
setting = self.safe_dict(settings, symbol, None)
if setting is None:
# NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max.
# When querying self endpoint, all markets with default margin and leverage settings on self account will return blank.
return self.parse_leverage_from_market(market)
else:
return self.parse_leverage_from_setting(symbol, setting)
def parse_leverage_from_setting(self, symbol: Str, setting: dict) -> Leverage:
# {
# "WLFI/USDC:USDC": {
# "symbol": "WLFI",
# "isolated": False,
# "leverage": 5,
# "created_at": 1758085929703,
# "updated_at": 1758086074002
# },
# }
isIsolated = self.safe_bool(setting, 'isolated', False)
leverage = self.safe_integer(setting, 'leverage')
marginMode = 'isolated' if isIsolated else 'cross'
return {
'info': setting,
'symbol': symbol,
'marginMode': marginMode,
'longLeverage': leverage,
'shortLeverage': leverage,
}
def parse_leverage_from_market(self, market: Market) -> Leverage:
marketLimits = self.safe_dict(market, 'limits', {})
leverageLimits = self.safe_dict(marketLimits, 'leverage', {})
return {
'info': market,
'symbol': self.safe_string(market, 'symbol'),
'marginMode': self.handle_option('fetchLeverage', 'defaultMarginMode', 'cross'),
'longLeverage': self.safe_integer(leverageLimits, 'max'),
'shortLeverage': self.safe_integer(leverageLimits, 'max'),
}
def fetch_account_settings(self, params={}) -> dict:
"""
fetch account's market settings. Settings are cached for walletAddress. To refresh the cache, call loadAccountSettings with refresh=true
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-settings
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: Dict repacked from list by symbol key
"""
userAccount = None
userAccount, params = self.handle_origin_and_single_address('fetchAccountSettings', params)
request: dict = {
'account': userAccount,
}
response = self.publicGetAccountSettings(self.extend(request, params))
# {
# "success": True,
# "data": [
# {
# "symbol": "WLFI",
# "isolated": False,
# "leverage": 5,
# "created_at": 1758085929703,
# "updated_at": 1758086074002
# }
# ],
# "error": null,
# "code": null
# }
return self.parse_account_settings(self.safe_list(response, 'data', []))
def load_account_settings(self, refresh: bool = False, params={}):
settings = self.handle_option('loadAccountSettings', 'settings', None)
if (settings is None) or (refresh is True):
self.options['settings'] = self.create_safe_dictionary()
settings = self.fetch_account_settings(params)
self.options['settings'] = settings
def parse_account_settings(self, settings: List[Any]) -> dict:
settingsLen = len(settings)
if settingsLen == 0:
return {}
settingsBySymbol = {}
for i in range(0, len(settings)):
marketId = settings[i]['symbol']
market = self.safe_market(marketId)
symbol = market['symbol']
settingsBySymbol[symbol] = settings[i]
return settingsBySymbol
def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
"""
fetches the margin mode of the trading pair
:param str symbol: unified symbol of the market to fetch the margin mode for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: a `margin mode structure <https://docs.ccxt.com/?id=margin-mode-structure>`
"""
self.load_account_settings()
userAccount = None
userAccount, params = self.handle_origin_and_single_address('fetchMarginMode', params)
cacheAddress = self.walletAddress
settings = None
if userAccount == cacheAddress:
settings = self.handle_option('fetchMarginMode', 'settings', None)
else:
request: dict = {
'account': userAccount,
}
settings = self.fetch_account_settings(self.extend(request, params))
# {
# "WLFI/USDC:USDC": {
# "symbol": "WLFI",
# "isolated": False,
# "leverage": 5,
# "created_at": 1758085929703,
# "updated_at": 1758086074002
# },
# }
setting = self.safe_dict(settings, symbol, None)
if setting is None:
# NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max.
# When querying self endpoint, all markets with default margin and leverage settings on self account will return blank.
return {
'symbol': symbol,
'marginMode': self.handle_option('fetchMarginMode', 'defaultMarginMode', 'cross'),
}
else:
return self.parse_margin_mode_from_setting(symbol, setting)
def parse_margin_mode_from_setting(self, symbol: Str, setting: dict) -> MarginMode:
# {
# "symbol": "WLFI",
# "isolated": False,
# "leverage": 5,
# "created_at": 1758085929703,
# "updated_at": 1758086074002
#
# }
isIsolated = self.safe_bool(setting, 'isolated', False)
marginMode = 'isolated' if isIsolated else 'cross'
return {
'symbol': symbol,
'marginMode': marginMode,
'info': setting,
}
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-orderbook
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.aggLevel]: aggregation level for price grouping. Defaults to 1. Can be 1, 10, 100, 1000, 10000
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/?id=order-book-structure>` indexed by market symbols
"""
self.load_markets()
market = self.market(symbol)
aggLevel = None
aggLevel, params = self.handle_option_and_params(params, 'fetchOrderBook', 'aggLevel', 1)
request: dict = {
'symbol': market['id'],
'agg_level': aggLevel,
}
response = self.publicGetBook(self.extend(request, params))
# {
# "success": True,
# "data": {
# "s": "BTC",
# "l": [
# [
# {
# "p": "106504",
# "a": "0.26203",
# "n": 1
# },
# {
# "p": "106498",
# "a": "0.29281",
# "n": 1
# }
# ],
# [
# {
# "p": "106559",
# "a": "0.26802",
# "n": 1
# },
# {
# "p": "106564",
# "a": "0.3002",
# "n": 1
# },
# ]
# ],
# "t": 1751370536325
# },
# "error": null,
# "code": null
# }
data = self.safe_dict(response, 'data', {})
levels = self.safe_list(data, 'l', [])
result: dict = {
'bids': self.safe_list(levels, 0, []),
'asks': self.safe_list(levels, 1, []),
}
timestamp = self.safe_integer(data, 't')
return self.parse_order_book(result, self.safe_symbol(None, market), timestamp, 'bids', 'asks', 'p', 'a')
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
"""
retrieves data on all swap markets for pacifica
:param str[] [symbols]: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
response = self.publicGetInfoPrices(params)
#
# {
# "success": True,
# "data": [
# {
# "funding": "0.00010529",
# "mark": "1.084819",
# "mid": "1.08615",
# "next_funding": "0.00011096",
# "open_interest": "3634796",
# "oracle": "1.084524",
# "symbol": "XPL",
# "timestamp": 1759222967974,
# "volume_24h": "20896698.0672",
# "yesterday_price": "1.3412"
# }
# ],
# "error": null,
# "code": null
# }
#
result = self.safe_list(response, 'data', [])
return self.parse_funding_rates(result, symbols)
def parse_funding_rate(self, info, market: Market = None) -> FundingRate:
#
# {
# "funding": "0.00010529",
# "mark": "1.084819",
# "mid": "1.08615",
# "next_funding": "0.00011096",
# "open_interest": "3634796",
# "oracle": "1.084524",
# "symbol": "XPL",
# "timestamp": 1759222967974,
# "volume_24h": "20896698.0672",
# "yesterday_price": "1.3412"
# }
#
marketId = self.safe_string(info, 'symbol')
market = self.safe_market(marketId, market)
symbol = market['symbol']
funding = self.safe_number(info, 'funding')
markPx = self.safe_number(info, 'mark')
oraclePx = self.safe_number(info, 'oracle')
nextFundingRate = self.safe_number(info, 'next_funding')
timestamp = self.safe_integer(info, 'timestamp')
fundingTimestamp = (int(math.floor(self.milliseconds()) / 60 / 60 / 1000) + 1) * 60 * 60 * 1000
return {
'info': info,
'symbol': symbol,
'markPrice': markPx,
'indexPrice': oraclePx,
'interestRate': None,
'estimatedSettlePrice': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'fundingRate': funding,
'fundingTimestamp': fundingTimestamp,
'fundingDatetime': self.iso8601(fundingTimestamp),
'nextFundingRate': nextFundingRate,
'nextFundingTimestamp': None,
'nextFundingDatetime': None,
'previousFundingRate': None,
'previousFundingTimestamp': None,
'previousFundingDatetime': None,
'interval': '1h',
}
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-candle-data
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents, support '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d'
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest candle to fetch. 'limit' is priority
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
if since is None:
raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "since" argument')
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "symbol" argument')
defaultMaxLimit = 3950 # 4000 by docs, but in fact >~3960 returns error
self.load_markets()
market = self.market(symbol)
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
if paginate:
return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, defaultMaxLimit)
tf = self.safe_string(self.timeframes, timeframe, timeframe)
request: dict = {
'symbol': market['id'],
'interval': tf,
'start_time': since,
}
request, params = self.handle_until_option('end_time', request, params)
nowMillis = self.milliseconds()
until = self.safe_integer(request, 'end_time')
if until is None:
if limit is not None:
until = since + (limit * (self.parse_timeframe(tf) * 1000)) - 1
if until is None:
until = since + (defaultMaxLimit * (self.parse_timeframe(tf) * 1000)) - 1
if until > nowMillis:
until = nowMillis
request['end_time'] = until
response = self.publicGetKline(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "t": 1748954160000,
# "T": 1748954220000,
# "s": "BTC",
# "i": "1m",
# "o": "105376",
# "c": "105376",
# "h": "105376",
# "l": "105376",
# "v": "0.00022",
# "n": 2
# }
# ],
# "error": null,
# "code": null
# }
#
candles = self.safe_list(response, 'data', [])
return self.parse_ohlcvs(candles, market, timeframe, since, limit)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# {
# "t": 1748954160000,
# "T": 1748954220000,
# "s": "BTC",
# "i": "1m",
# "o": "105376",
# "c": "105376",
# "h": "105376",
# "l": "105376",
# "v": "0.00022",
# "n": 2
# }
#
return [
self.safe_integer(ohlcv, 't'),
self.safe_number(ohlcv, 'o'),
self.safe_number(ohlcv, 'h'),
self.safe_number(ohlcv, 'l'),
self.safe_number(ohlcv, 'c'),
self.safe_number(ohlcv, 'v'),
]
def fetch_trades(self, symbol: Str, since: Int = None, limit: Int = None, params={}):
"""
get the list of most recent trades for a particular symbol
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-recent-trades
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trades structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
response = self.publicGetTrades(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "event_type": "fulfill_taker",
# "price": "104721",
# "amount": "0.0001",
# "side": "close_long",
# "cause": "normal",
# "created_at": 1765006315306
# }
# ],
# "error": null,
# "code": null,
# "last_order_id": 1557404170
# }
#
recentTrades = self.safe_list(response, 'data', [])
return self.parse_trades(recentTrades, market, since, limit)
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
fetch all trades made by the user
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-trade-history
:param str [symbol]: unified market symbol
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trades structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest trade
:param str [params.account]: will default to walletAddress if not provided
:param str [params.cursor]: pagination cursor from prev request(manual use)
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
"""
self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate', False)
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchMyTrades', params)
defaultLimit = 100 # Default max limit
if paginate:
return self.fetch_paginated_call_cursor('fetchMyTrades', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
request: dict = {}
request, params = self.handle_until_option('end_time', request, params)
request['account'] = userAddress
if symbol is not None:
request['symbol'] = market['id']
if limit is not None:
request['limit'] = limit
if since is not None:
request['start_time'] = since
response = self.publicGetTradesHistory(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "history_id": 19329801,
# "order_id": 315293920,
# "client_order_id": "acf...",
# "symbol": "LDO",
# "amount": "0.1",
# "price": "1.1904",
# "entry_price": "1.176247",
# "fee": "0",
# "pnl": "-0.001415",
# "event_type": "fulfill_maker",
# "side": "close_short",
# "created_at": 1759215599188,
# "cause": "normal"
# },
# ...
# ],
# "next_cursor": "11111Z5RK", # not included to info!
# "has_more": True # not included to info!
# }
#
data = self.add_pagination_cursor_to_result(response)
return self.parse_trades(data, market, since, limit)
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
#
# user trades:
# {
# "history_id": 19329801,
# "order_id": 315293920,
# "client_order_id": "acf...",
# "symbol": "LDO",
# "amount": "0.1",
# "price": "1.1904",
# "entry_price": "1.176247",
# "fee": "0",
# "pnl": "-0.001415",
# "event_type": "fulfill_maker",
# "side": "close_short",
# "created_at": 1759215599188,
# "cause": "normal"
# },
# recent public trades:
# {
# "event_type": "fulfill_taker",
# "price": "104721",
# "amount": "0.0001",
# "side": "close_long",
# "cause": "normal",
# "created_at": 1765006315306
# }
#
eventType = self.safe_string(trade, 'event_type')
timestamp = self.safe_integer(trade, 'created_at')
price = self.safe_string(trade, 'price')
amount = self.safe_string(trade, 'amount')
symbol = self.safe_symbol(None, market)
id = self.safe_string(trade, 'history_id')
side = self.safe_string(trade, 'side')
if side == 'open_long':
side = 'buy'
elif side == 'close_long':
side = 'sell'
elif side == 'open_short':
side = 'sell'
elif side == 'close_short':
side = 'buy'
fee = self.safe_string(trade, 'fee')
orderId = self.safe_string(trade, 'order_id')
takerOrMaker = None
if eventType is not None:
takerOrMaker = 'maker' if (eventType == 'fulfill_maker') else 'taker'
# public trades have no orderId
if orderId is None:
takerOrMaker = None
return self.safe_trade({
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': orderId,
'type': None,
'side': side,
'takerOrMaker': takerOrMaker,
'price': price,
'amount': amount,
'cost': None,
'fee': {
'cost': fee,
'currency': 'USDC',
'rate': None,
},
}, market)
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
create a trade order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of currency you want to trade in units of base currency. Not used for set tpsl order!
:param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param float [params.triggerPrice]: The price a trigger order is triggered at
:param float [params.stopLossPrice]: the price that a stop loss order is triggered at(optional provide stopLossCloid)
:param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid)
:param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book)
:param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
:param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: an `order structure <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
request, operationType = self.create_order_request(symbol, type, side, amount, price, params)
params = self.omit(params, [
'reduceOnly', 'clientOrderId', 'stopLimitPrice', 'timeInForce', 'triggerPrice', 'stopLossCloid',
'stopLossPrice', 'stopLossLimitPrice', 'takeProfitCloid', 'takeProfitPrice', 'takeProfitLimitPrice', 'expiryWindow',
])
response = None
if operationType == 'create_market_order':
response = self.privatePostOrdersCreateMarket(self.extend(request, params))
elif operationType == 'create_stop_order':
response = self.privatePostOrdersStopCreate(self.extend(request, params))
elif operationType == 'set_position_tpsl':
response = self.privatePostPositionsTpsl(self.extend(request, params))
else: # create_order
response = self.privatePostOrdersCreate(self.extend(request, params))
#
# {
# 'success': True,
# 'data': {
# "order_id": 12345
# },
# }
#
success = self.safe_bool(response, 'success', False)
status = None
if not success:
status = 'rejected'
else:
status = 'open'
order = self.safe_dict(response, 'data', {})
orderId = self.safe_string(order, 'order_id')
return self.safe_order({'id': orderId, 'status': status, 'info': response, 'symbol': symbol})
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
@ignore
create a trade order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of currency you want to trade in units of base currency
:param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders, but can be used of Trigger Order.
:param dict [params]: extra parameters specific to the exchange API endpoint
:param float [params.triggerPrice]: The price a trigger order is triggered at
:param float [params.stopLossPrice]: the price that a stop loss order is triggered at(optional provide stopLossCloid)
:param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid)
:param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book)
:param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
:param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: an [order structure]
"""
market = self.market(symbol)
sigPayload: dict = {
'symbol': market['id'],
'side': self.map_side(side),
}
operationType = None
reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only', False)
orderType = type.upper()
triggerPrice = self.safe_string(params, 'triggerPrice')
stopLossPrice = self.safe_string(params, 'stopLossPrice')
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
tifRaw = self.safe_string_upper(params, 'timeInForce')
isMarket = orderType == 'MARKET'
isTakeProfitOrder = (takeProfitPrice is not None)
isStopLossOrder = (stopLossPrice is not None)
isStopOrder = (triggerPrice is not None)
timeInForce = self.map_time_in_force(tifRaw)
if isMarket:
operationType = 'create_market_order'
sigPayload['reduce_only'] = reduceOnly
defaultSlippage = self.handle_option('createOrder', 'defaultSlippage', '0.5')
slippage = self.safe_string_2(params, 'slippage', 'slippage_percent', defaultSlippage)
sigPayload['slippage_percent'] = slippage
elif (isTakeProfitOrder or isStopLossOrder) and (price is None): # the tpsl endpoint does not accept a 'price' parameter
operationType = 'set_position_tpsl'
elif isStopOrder:
operationType = 'create_stop_order'
sigPayload['reduce_only'] = reduceOnly
stopClientOrderId = self.safe_string(params, 'clientOrderId')
params = self.omit(params, ['clientOrderId'])
stopPayload = {
'amount': self.amount_to_precision(symbol, amount),
'stop_price': self.price_to_precision(symbol, triggerPrice),
}
if stopClientOrderId is not None:
stopPayload['client_order_id'] = stopClientOrderId
if price is not None:
stopPayload['limit_price'] = self.price_to_precision(symbol, price)
sigPayload['stop_order'] = stopPayload
else:
operationType = 'create_order'
sigPayload['reduce_only'] = reduceOnly
if timeInForce is None:
sigPayload['tif'] = 'GTC'
else:
sigPayload['tif'] = timeInForce
if isTakeProfitOrder:
tpPayload: dict = {
'stop_price': self.price_to_precision(symbol, takeProfitPrice),
}
if price is not None:
tpPayload['limit_price'] = self.price_to_precision(symbol, price)
sigPayload['take_profit'] = tpPayload
if isStopLossOrder:
slPayload: dict = {
'stop_price': self.price_to_precision(symbol, stopLossPrice),
}
if price is not None:
slPayload['limit_price'] = self.price_to_precision(symbol, price)
sigPayload['stop_loss'] = slPayload
if price is not None and operationType == 'create_order':
sigPayload['price'] = self.price_to_precision(symbol, price)
if amount is not None and (operationType != 'create_stop_order' and operationType != 'set_position_tpsl'):
sigPayload['amount'] = self.amount_to_precision(symbol, amount)
clientOrderId = self.safe_string_n(params, ['clientOrderId'])
if clientOrderId is not None:
sigPayload['client_order_id'] = clientOrderId
request = self.post_action_request(operationType, sigPayload, params)
return [request, operationType]
def batch_orders_request(self, actions: List[Any]):
#
# [
# {
# "type":"Create",
# "data":{
# "account":"42trU9A5...",
# "signature":"5UpRZ14Q...",
# "timestamp":1749190500355,
# "expiry_window":5000,
# "symbol":"BTC",
# "price":"100000",
# "reduce_only":false,
# "amount":"0.1",
# "side":"bid",
# "tif":"GTC",
# "client_order_id":"57a5efb1-bb96-49a5-8bfd-f25d5f22bc7e"
# }
# },
# {
# "type":"Cancel",
# "data":{
# "account":"42trU9A5...",
# "signature":"4NDFHyTG...",
# "timestamp":1749190500355,
# "expiry_window":5000,
# "symbol":"BTC",
# "order_id":42069
# }
# }
# ]
#
# Create(Only Limit or Market, never stop order or tpsl order)
# Cancel(Only common(limit) orders)
#
lenActions = len(actions)
maxLen = self.handle_option('batchOrdersRequest', 'batchOrdersMax')
if maxLen is not None:
if lenActions > maxLen:
raise ExchangeError(self.id + ' batchOrdersRequest() too many orders to create/cancel. Limit is ' + maxLen)
return {
'actions': actions,
}
def create_orders_request(self, orders: List[OrderRequest], params={}):
actions = []
timestamp = self.milliseconds() # unified sequence
for i in range(0, len(orders)):
order = orders[i]
symbol = self.safe_string(order, 'symbol')
side = self.safe_string(order, 'side')
price = self.safe_string(order, 'price')
type = self.safe_string(order, 'type', 'limit')
orderParams = self.safe_dict(order, 'params', {})
orderParams['timestamp'] = timestamp
amount = self.safe_string(order, 'amount')
amountNumber = self.parse_number(amount)
priceNumber = self.parse_number(price)
if type != 'limit':
raise NotSupported(self.id + ' createOrders() supports only type = "limit"! Your value type=' + type)
requestList = self.create_order_request(symbol, type, side, amountNumber, priceNumber, orderParams)
action = {
'type': 'Create',
'data': requestList[0],
}
actions.append(action)
return self.batch_orders_request(actions)
def create_orders(self, orders: List[OrderRequest], params={}):
"""
create a list of trade orders. It is supports only limit orders and have a random jitter ~100-300ms!
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type(optional or 'limit'), side, amount, price and params
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `order structure <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
request = self.create_orders_request(orders)
response = self.privatePostOrdersBatch(self.extend(request, params))
# {
# "success": True,
# "data": {
# "results": [
# {
# "success": True,
# "order_id": 470506,
# "error": null
# },
# {
# "success": True,
# }
# ]
# },
# "error": null,
# "code": null
# }
#
data = self.safe_dict(response, 'data', {})
results = self.safe_list(data, 'results', [])
ordersToReturn = []
for i in range(0, len(results)):
order = results[i]
error = self.safe_string(order, 'error', None)
success = self.safe_bool(order, 'success', False)
status = None
if (error is not None) or (not success):
status = 'rejected'
else:
status = 'open'
orderId = self.safe_string(order, 'order_id')
ordersToReturn.append(self.safe_order({'info': order, 'id': orderId, 'status': status}))
return ordersToReturn
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
"""
cancel multiple orders
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order
:param str[] ids: order ids. An ids list is always required(can be empty). Both ids and clientOrderIds can be passed simultaneously.
:param str [symbol]: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param string|str[] [params.clientOrderIds]: client order ids,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: an list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrders() requires a "symbol" argument!')
request = self.cancel_orders_request(ids, symbol, params)
params = self.omit(params, ['expiryWindow', 'clientOrderIds'])
response = self.privatePostOrdersBatch(self.extend(request, params))
#
# {
# "success": True,
# "data": {
# "results": [
# {
# "success": True,
# "order_id": 470506,
# "error": null
# },
# {
# "success": True,
# }
# ]
# },
# "error": null,
# "code": null
# }
#
data = self.safe_dict(response, 'data', {})
results = self.safe_list(data, 'results', [])
ordersToReturn = []
for i in range(0, len(results)):
order = results[i]
error = self.safe_string(order, 'error', None)
success = self.safe_bool(order, 'success', False)
status = None
if (error is not None) or (not success):
status = 'closed'
else:
status = 'canceled'
ordersToReturn.append(self.safe_order({'info': order, 'status': status, 'symbol': symbol}))
return ordersToReturn
def cancel_orders_request(self, ids: List[Str], symbol: Str = None, params={}):
actions = []
for i in range(0, len(ids)):
id = ids[i]
request = self.cancel_order_request(id, symbol, params)
action = {
'type': 'Cancel',
'data': request,
}
actions.append(action)
clientOrderIds = self.safe_list(params, 'clientOrderIds', [])
params = self.omit(params, 'clientOrderIds')
for i in range(0, len(clientOrderIds)):
cloid = clientOrderIds[i]
cloidParams = {
'clientOrderId': cloid,
}
request = self.cancel_order_request(cloid, symbol, self.extend(cloidParams, params))
action = {
'type': 'Cancel',
'data': request,
}
actions.append(action)
return self.batch_orders_request(actions)
def cancel_all_orders(self, symbol: Str = None, params={}):
"""
cancel all open orders in a market
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-all-orders
:param str symbol:(optional) unified market symbol of the market to cancel orders in.
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.excludeReduceOnly]: whether to exclude reduce-only orders
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
request = self.cancel_all_orders_request(symbol, params)
params = self.omit(params, ['excludeReduceOnly', 'expiryWindow'])
response = self.privatePostOrdersCancelAll(self.extend(request, params))
#
# {
# success: True,
# data: {
# "cancelled_count": 5,
# },
# code: null,
# error: null
# }
#
return [
self.safe_order({
'info': response,
}),
]
def cancel_all_orders_request(self, symbol: Str, params={}):
operationType = 'cancel_all_orders'
sigPayload: dict = {}
excludeReduceOnly = self.safe_bool(params, 'excludeReduceOnly', False)
sigPayload['exclude_reduce_only'] = excludeReduceOnly
if symbol is not None:
market = self.market(symbol)
sigPayload['all_symbols'] = False
sigPayload['symbol'] = market['id']
else:
sigPayload['all_symbols'] = True
request = self.post_action_request(operationType, sigPayload, params)
return request
def cancel_order(self, id: str, symbol: Str = None, params={}):
"""
cancels an open order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-stop-order#response
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-order
:param str id: order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.stop]: necessary if self is to cancel a stop order.
:param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: An `order structure <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
request = self.cancel_order_request(id, symbol, params)
isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False)
params = self.omit(params, ['expiryWindow', 'trigger', 'stop', 'clientOrderId'])
response = None
if isStopOrder:
response = self.privatePostOrdersStopCancel(self.extend(request, params))
else:
response = self.privatePostOrdersCancel(self.extend(request, params))
#
# response:
# {
# "success": True,
# "data": null
# }
#
success = self.safe_bool(response, 'success', False)
status = 'canceled' if success else 'closed'
return self.safe_order({'id': id, 'status': status, 'info': response, 'symbol': symbol})
def cancel_order_request(self, id: Str, symbol: Str = None, params={}):
market = self.market(symbol)
isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False)
operationType = None
if isStopOrder:
operationType = 'cancel_stop_order'
else:
operationType = 'cancel_order'
clientOrderId = self.safe_string(params, 'clientOrderId')
sigPayload: dict = {
'symbol': market['id'],
}
if clientOrderId is not None:
sigPayload['client_order_id'] = clientOrderId
else:
sigPayload['order_id'] = self.parse_to_int(id)
request = self.post_action_request(operationType, sigPayload, params)
return request
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
"""
edit a trade order
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/edit-order
:param str id: edit order id
:param str symbol: unified symbol of the market to edit an order in
:param str type: 'market' or 'limit' WARN is not usable!
:param str side: 'buy' or 'sell' WARN is not usable!
:param float amount: how much of currency you want to trade in units of base currency
:param float price: the price at which the order is to be fulfilled, in units of the quote currency
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: an `order structure <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
self.initialize_client()
market = self.market(symbol)
request = self.edit_order_request(id, symbol, type, side, amount, price, market, params)
params = self.omit(params, ['expiryWindow', 'clientOrderId'])
response = self.privatePostOrdersEdit(self.extend(request, params))
#
# {
# 'data': {
# "order_id": 123498765
# }
# }
#
data = self.safe_dict(response, 'data', {})
orderId = self.safe_string(data, 'order_id')
return self.safe_order({'id': orderId, 'info': response, 'symbol': symbol})
def edit_order_request(self, id: str, symbol: str, type: str, side: str, amount: Num, price: Num, market: Market, params={}):
if amount is None:
raise ArgumentsRequired(self.id + ' editOrder() requires an amount!')
if price is None:
raise ArgumentsRequired(self.id + ' editOrder() requires a price')
operationType = 'edit_order'
clientOrderId = self.safe_string(params, 'clientOrderId')
priceNormalized = self.price_to_precision(symbol, price)
amountNormalized = self.amount_to_precision(symbol, amount)
sigPayload: dict = {
'symbol': market['id'],
'price': priceNormalized,
'amount': amountNormalized,
}
if (clientOrderId is None) and (id is None):
raise ArgumentsRequired('self.id' + 'editOrder() requires either "id" or "clientOrderId"')
if clientOrderId is not None:
sigPayload['client_order_id'] = clientOrderId
else:
sigPayload['order_id'] = self.parse_to_int(id)
request = self.post_action_request(operationType, sigPayload, params)
return request
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical funding rate prices
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-historical-funding
:param str symbol: unified symbol of the market to fetch the funding rate history for
:param int [since]: timestamp in ms of the earliest funding rate to fetch
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/?id=funding-rate-history-structure>` to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.cursor]: pagination cursor from prev request(manual use)
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/?id=funding-rate-history-structure>`
"""
self.load_markets()
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
market = self.market(symbol)
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate', False)
defaultLimit = 100 # Default max limit
if paginate:
return self.fetch_paginated_call_cursor('fetchFundingRateHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
request: dict = {
'symbol': market['id'],
}
if limit is not None:
request['limit'] = limit
response = self.publicGetFundingRateHistory(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "oracle_price": "117170.410304",
# "bid_impact_price": "117126",
# "ask_impact_price": "117142",
# "funding_rate": "0.0000125",
# "next_funding_rate": "0.0000125",
# "created_at": 1753806934249
# },
# ...
# ],
# "next_cursor": "11114Lz77",
# "has_more": True
# }
#
data = self.add_pagination_cursor_to_result(response)
result = []
for i in range(0, len(data)):
entry = data[i]
timestamp = self.safe_integer(entry, 'created_at')
result.append({
'info': entry,
'symbol': market['symbol'],
'fundingRate': self.safe_number(entry, 'funding_rate'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
})
sorted = self.sort_by(result, 'timestamp')
return self.filter_by_since_limit(sorted, since, limit, 'timestamp')
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-prices
:param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/?id=ticker-structure>`
"""
self.load_markets()
symbols = self.market_symbols(symbols)
response = self.publicGetInfoPrices(params)
#
# {
# "success": True,
# "data": [
# {
# "funding": "0.00010529",
# "mark": "1.084819",
# "mid": "1.08615",
# "next_funding": "0.00011096",
# "open_interest": "3634796",
# "oracle": "1.084524",
# "symbol": "XPL",
# "timestamp": 1759222967974,
# "volume_24h": "20896698.0672",
# "yesterday_price": "1.3412"
# }
# ],
# "error": null,
# "code": null
# }
#
data = self.safe_list(response, 'data', [])
result: dict = {}
for i in range(0, len(data)):
info = data[i]
ticker = self.parse_ticker(info)
symbol = self.safe_string(ticker, 'symbol')
result[symbol] = ticker
return self.filter_by_array_tickers(result, 'symbol', symbols)
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
#
# {
# "funding": "0.00010529",
# "mark": "1.084819",
# "mid": "1.08615",
# "next_funding": "0.00011096",
# "open_interest": "3634796",
# "oracle": "1.084524",
# "symbol": "XPL",
# "timestamp": 1759222967974,
# "volume_24h": "20896698.0672",
# "yesterday_price": "1.3412"
# }
#
marketId = self.safe_string(ticker, 'symbol')
market = self.safe_market(marketId, market)
symbol = market['symbol']
timestamp = self.safe_integer(ticker, 'timestamp')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'previousClose': self.safe_number(ticker, 'yesterday_price'),
'close': self.safe_number(ticker, 'mid'),
'bid': None,
'ask': None,
'quoteVolume': self.safe_number(ticker, 'volume_24h'),
'info': ticker,
}, market)
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently closed orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['closed'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all canceled orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['canceled'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all closed and canceled orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['canceled', 'closed', 'rejected'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently open orders
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-open-orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchOpenOrders', params)
request: dict = {
'account': userAddress,
}
market = None
if symbol is not None:
market = self.market(symbol)
response = self.publicGetOrders(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "order_id": 315979358,
# "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479",
# "symbol": "ASTER",
# "side": "ask",
# "price": "1.836",
# "initial_amount": "85.33",
# "filled_amount": "0",
# "cancelled_amount": "0",
# "stop_price": null,
# "order_type": "limit",
# "stop_parent_order_id": null,
# "reduce_only": False,
# "created_at": 1759224706737,
# "updated_at": 1759224706737
# }
# ],
# "error": null,
# "code": null,
# "last_order_id": 1557370337
# }
#
data = self.safe_list(response, 'data', [])
return self.parse_orders(data, market, since, limit)
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all orders
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:param str [params.cursor]: pagination cursor from prev request(manual use)
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate', False)
defaultLimit = 100 # max default 100
if paginate:
return self.fetch_paginated_call_cursor('fetchOrders', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchOrders', params)
market = None
if symbol is not None:
market = self.market(symbol)
request: dict = {
'account': userAddress,
}
if limit is not None:
request['limit'] = limit
response = self.publicGetOrdersHistory(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "order_id": 315992721,
# "client_order_id": "ade",
# "symbol": "XPL",
# "side": "ask",
# "initial_price": "1.0865",
# "average_filled_price": "0",
# "amount": "984",
# "filled_amount": "0",
# "order_status": "open",
# "order_type": "limit",
# "stop_price": null,
# "stop_parent_order_id": null,
# "reduce_only": False,
# "reason": null,
# "created_at": 1759224893638,
# "updated_at": 1759224893638
# },
# ...
# ],
# "next_cursor": "1111Hyd74",
# "has_more": True
# }
#
data = self.add_pagination_cursor_to_result(response)
orders = self.parse_orders(data, market, since, limit)
return orders
def add_pagination_cursor_to_result(self, response):
data = self.safe_list(response, 'data', [])
paginationCursor = self.safe_string(response, 'next_cursor')
hasMore = self.safe_bool(response, 'has_more', False)
dataLength = len(data)
if hasMore:
if (paginationCursor is not None) and (dataLength > 0):
first = data[0]
first['next_cursor'] = paginationCursor
first['has_more'] = hasMore
data[0] = first
return data
def fetch_order(self, id: str, symbol: Str = None, params={}):
"""
fetches information on an order made by the user
https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history-by-id
:param str id: order id
:param str symbol:(optional) unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: An `order structure <https://docs.ccxt.com/?id=order-structure>`
"""
self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
request: dict = {
'order_id': id,
}
response = self.publicGetOrdersHistoryById(self.extend(request, params))
#
# {
# "success": True,
# "data": [
# {
# "history_id": 641452639,
# "order_id": 315992721,
# "client_order_id": "ade1aa6...",
# "symbol": "XPL",
# "side": "ask",
# "price": "1.0865",
# "initial_amount": "984",
# "filled_amount": "0",
# "cancelled_amount": "984",
# "event_type": "cancel",
# "order_type": "limit",
# "order_status": "cancelled",
# "stop_price": null,
# "stop_parent_order_id": null,
# "reduce_only": False,
# "created_at": 1759224895038
# },
# {
# "history_id": 641452513,
# "order_id": 315992721,
# "client_order_id": "ade1aa6...",
# "symbol": "XPL",
# "side": "ask",
# "price": "1.0865",
# "initial_amount": "984",
# "filled_amount": "0",
# "cancelled_amount": "0",
# "event_type": "make",
# "order_type": "limit",
# "order_status": "open",
# "stop_price": null,
# "stop_parent_order_id": null,
# "reduce_only": False,
# "created_at": 1759224893638
# }
# ],
# "error": null,
# "code": null
# }
#
data = self.safe_list(response, 'data', [])
# return last state
sorted = self.sort_by(data, 'created_at')
lastIdx = len(sorted)
lastInfo = {}
if lastIdx > 0:
lastInfo = sorted[0]
return self.parse_order(lastInfo, market)
def parse_order_status(self, status: Str):
statuses: dict = {
'open': 'open',
'partially_filled': 'open',
'filled': 'closed',
'cancelled': 'canceled',
'rejected': 'failed',
}
return self.safe_string(statuses, status, status)
def map_time_in_force(self, tifRaw: Str):
tifMap: dict = {
'GTC': 'GTC',
'IOC': 'IOC',
'PO': 'ALO',
'POST_ONLY': 'ALO',
'PO_TOB': 'TOB',
'TOB': 'TOB',
'ALO': 'ALO',
}
tif = None
if tifRaw is not None:
tif = tifRaw.upper()
return self.safe_string(tifMap, tif, None)
def map_side(self, sideRaw: str):
sideMap: dict = {
'sell': 'ask',
'buy': 'bid',
}
return self.safe_string(sideMap, sideRaw, sideRaw)
def parse_order_type(self, status: str):
statuses: dict = {
'stop_limit': 'limit',
'stop_market': 'market',
'take_profit_limit': 'limit',
'stop_loss_limit': 'limit',
'take_profit_market': 'market',
'stop_loss_market': 'market',
}
return self.safe_string(statuses, status, status)
def parse_order(self, order: dict, market: Market = None) -> Order:
#
# fetchOpenOrders
# [
# {
# "order_id": 315979358,
# "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479",
# "symbol": "ASTER",
# "side": "ask",
# "price": "1.836",
# "initial_amount": "85.33",
# "filled_amount": "0",
# "cancelled_amount": "0",
# "stop_price": null,
# "order_type": "limit",
# "stop_parent_order_id": null,
# "reduce_only": False,
# "created_at": 1759224706737,
# "updated_at": 1759224706737
# }
# ],
#
# fetchOrders
# [
# {
# "order_id": 315992721,
# "client_order_id": "ade",
# "symbol": "XPL",
# "side": "ask",
# "initial_price": "1.0865",
# "average_filled_price": "0",
# "amount": "984",
# "filled_amount": "0",
# "order_status": "open",
# "order_type": "limit",
# "stop_price": null,
# "stop_parent_order_id": null,
# "reduce_only": False,
# "reason": null,
# "created_at": 1759224893638,
# "updated_at": 1759224893638
# },
# ]
#
# fetchOrder
# {
# "history_id": 641452639,
# "order_id": 315992721,
# "client_order_id": "ade1aa6...",
# "symbol": "XPL",
# "side": "ask",
# "price": "1.0865",
# "initial_amount": "984",
# "filled_amount": "0",
# "cancelled_amount": "984",
# "event_type": "cancel",
# "order_type": "limit",
# "order_status": "cancelled",
# "stop_price": null,
# "stop_parent_order_id": null,
# "reduce_only": False,
# "created_at": 1759224895038
# }
#
# websocket watchOrders
# {
# "i": 1559665358,
# "I": null,
# "u": "BrZp5bidJ3WUvceSq7X78bhjTfZXeezzGvGEV4hAYKTa",
# "s": "BTC",
# "d": "bid",
# "p": "89501",
# "ip": "89501",
# "lp": "89501",
# "a": "0.00012",
# "f": "0.00012",
# "oe": "fulfill_limit",
# "os": "filled",
# "ot": "limit",
# "sp": null,
# "si": null,
# "r": False,
# "ct": 1765017049008,
# "ut": 1765017219639,
# "li": 1559696133
# }
#
marketId = self.safe_string_2(order, 'symbol', 's')
symbol = None
if symbol is not None:
market = self.safe_market(marketId, market)
symbol = market['symbol']
timestamp = self.safe_integer_2(order, 'created_at', 'ct')
status = self.safe_string_2(order, 'order_status', 'os', 'open') # open if method is fetchOpenOrders
side = self.safe_string(order, 'side', 'd')
if side is not None:
side = 'buy' if (side == 'bid') else 'sell'
totalAmount = self.safe_string_2(order, 'initial_amount', 'a')
filledAmount = self.safe_string_2(order, 'filled_amount', 'f')
remaining = Precise.string_sub(totalAmount, filledAmount)
return self.safe_order({
'info': order,
'id': self.safe_string_2(order, 'order_id', 'i'),
'clientOrderId': self.safe_string_2(order, 'client_order_id', 'I'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': None,
'lastUpdateTimestamp': self.safe_integer_2(order, 'updated_at', 'ut'),
'symbol': symbol,
'type': self.parse_order_type(self.safe_string_lower_2(order, 'order_type', 'ot')),
'timeInForce': None,
'postOnly': None,
'reduceOnly': self.safe_bool_2(order, 'reduce_only', 'r'),
'side': side,
'price': self.safe_string_2(order, 'price', 'lp'),
'triggerPrice': self.safe_number_2(order, 'stop_price', 'sp'),
'amount': totalAmount,
'cost': None,
'average': self.safe_string_2(order, 'average_filled_price', 'p'),
'filled': filledAmount,
'remaining': remaining,
'status': self.parse_order_status(status),
'fee': None,
'trades': None,
}, market)
def fetch_position(self, symbol: str, params={}):
"""
fetch data on an open position
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-positions
:param str symbol: unified market symbol of the market the position is held in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: a `position structure <https://docs.ccxt.com/?id=position-structure>`
"""
positions = self.fetch_positions([symbol], params)
return self.safe_dict(positions, 0, {})
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
"""
fetch all open positions
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-positions
:param str[] [symbols]: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict[]: a list of `position structure <https://docs.ccxt.com/?id=position-structure>`
"""
self.load_markets()
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchPositions', params)
symbols = self.market_symbols(symbols)
request: dict = {
'account': userAddress,
}
response = self.publicGetPositions(self.extend(request, params))
# {
# "success": True,
# "data": [
# {
# "symbol": "AAVE",
# "side": "ask",
# "amount": "223.72",
# "entry_price": "279.283134",
# "margin": "0", # only shown for isolated margin
# "funding": "13.159593",
# "isolated": False,
# "created_at": 1754928414996,
# "updated_at": 1759223365538
# }
# ],
# "error": null,
# "code": null,
# "last_order_id": 1557431179
# }
data = self.safe_list(response, 'data', [])
result = []
for i in range(0, len(data)):
result.append(self.parse_position(data[i], None))
return self.filter_by_array_positions(result, 'symbol', symbols, False)
def parse_position(self, position: dict, market: Market = None):
#
# {
# "symbol": "AAVE",
# "side": "ask",
# "amount": "223.72",
# "entry_price": "279.283134",
# "margin": "0", # only shown for isolated margin
# "funding": "13.159593",
# "isolated": False,
# "created_at": 1754928414996,
# "updated_at": 1759223365538
# }
#
marketId = self.safe_string(position, 'symbol')
market = self.safe_market(marketId, market)
symbol = market['symbol']
margin = self.safe_string(position, 'margin')
marginMode = 'isolated' if (margin is not None and margin != '0') else 'cross'
isIsolated = (marginMode == 'isolated')
side = self.safe_string(position, 'side')
if side is not None:
side = 'long' if (side == 'bid') else 'short'
createdAt = self.safe_integer(position, 'created_at')
return self.safe_position({
'info': position,
'id': None,
'symbol': symbol,
'timestamp': createdAt,
'datetime': self.iso8601(createdAt),
'isolated': isIsolated,
'hedged': None,
'side': side,
'contracts': self.safe_number(position, 'amount'),
'contractSize': None,
'entryPrice': self.safe_number(position, 'entry_price'),
'markPrice': None,
'notional': None,
'leverage': None,
'collateral': margin,
'initialMargin': None,
'maintenanceMargin': None,
'initialMarginPercentage': None,
'maintenanceMarginPercentage': None,
'unrealizedPnl': None,
'liquidationPrice': None,
'marginMode': marginMode,
'percentage': None,
})
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
"""
set margin mode(symbol)
https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-margin-mode
:param str marginMode: margin mode must be either [isolated, cross]
:param str symbol: unified market symbol of the market the position is held in, default is None
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: response from the exchange
"""
operationType = 'update_margin_mode'
if symbol is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
self.load_markets()
market = self.market(symbol)
isIsolated = (marginMode == 'isolated')
sigPayload: dict = {
'symbol': market['id'],
'is_isolated': isIsolated,
}
request = self.post_action_request(operationType, sigPayload, params)
params = self.omit(params, ['expiryWindow'])
response = self.privatePostAccountMargin(request)
# {
# "success": True
# }
return response
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
"""
set the level of leverage for a market
https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-leverage
:param float leverage: the rate of leverage
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: response from the exchange
"""
operationType = 'update_leverage'
if symbol is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
self.load_markets()
market = self.market(symbol)
sigPayload: dict = {
'symbol': market['id'],
'leverage': leverage,
}
request = self.post_action_request(operationType, sigPayload, params)
params = self.omit(params, ['expiryWindow'])
response = self.privatePostAccountLeverage(request)
# {
# "success": True
# }
return response
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
"""
make a withdrawal(only support native USDC)
https://docs.pacifica.fi/api-documentation/api/rest-api/account/request-withdrawal
:param str code: unified currency code
:param float amount: the amount to withdraw
:param str address: the address to withdraw to
:param str tag:
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: a `transaction structure <https://docs.ccxt.com/?id=transaction-structure>`
"""
operationType = 'withdraw'
self.load_markets()
self.check_address(address)
sigPayload: dict = {
'amount': str(amount),
}
request = self.post_action_request(operationType, sigPayload, params)
params = self.omit(params, ['expiryWindow'])
response = self.privatePostAccountWithdraw(self.extend(request, params))
return {'info': response}
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
"""
fetch the trading fees for a market
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-info
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:returns dict: a `fee structure <https://docs.ccxt.com/?id=fee-structure>`
"""
self.load_markets()
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchTradingFee', params)
market = self.market(symbol)
request: dict = {
'account': userAddress,
}
response = self.publicGetAccount(self.extend(request, params))
# {
# "success": True,
# "data": {
# "balance": "2000.000000",
# "fee_level": 0,
# "maker_fee": "0.00015",
# "taker_fee": "0.0004",
# "account_equity": "2150.250000",
# "available_to_spend": "1800.750000",
# "available_to_withdraw": "1500.850000",
# "pending_balance": "0.000000",
# "total_margin_used": "349.500000",
# "cross_mmr": "420.690000",
# "positions_count": 2,
# "orders_count": 3,
# "stop_orders_count": 1,
# "updated_at": 1716200000000,
# "use_ltp_for_stop_orders": False
# },
# "error": null,
# "code": null
# }
data: dict = self.safe_dict(response, 'data', {})
return self.parse_trading_fee(data, market)
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
#
# {
# "balance": "2000.000000",
# "fee_level": 0,
# "maker_fee": "0.00015",
# "taker_fee": "0.0004",
# "account_equity": "2150.250000",
# "available_to_spend": "1800.750000",
# "available_to_withdraw": "1500.850000",
# "pending_balance": "0.000000",
# "total_margin_used": "349.500000",
# "cross_mmr": "420.690000",
# "positions_count": 2,
# "orders_count": 3,
# "stop_orders_count": 1,
# "updated_at": 1716200000000,
# "use_ltp_for_stop_orders": False
# }
#
#
symbol = self.safe_symbol(None, market)
return {
'info': fee,
'symbol': symbol,
'maker': self.safe_number(fee, 'maker_fee'),
'taker': self.safe_number(fee, 'taker_fee'),
'percentage': None,
'tierBased': None,
}
def fetch_open_interests(self, symbols: Strings = None, params={}):
"""
Retrieves the open interest for a list of symbols
:param str[] [symbols]: Unified CCXT market symbol
:param dict [params]: exchange specific parameters
:returns dict} an open interest structure{@link https://docs.ccxt.com/?id=open-interest-structure:
"""
self.load_markets()
symbols = self.market_symbols(symbols)
swapMarkets = self.fetch_swap_markets()
return self.parse_open_interests(swapMarkets, symbols)
def fetch_open_interest(self, symbol: str, params={}):
"""
retrieves the open interest of a contract trading pair
:param str symbol: unified CCXT market symbol
:param dict [params]: exchange specific parameters
:returns dict: an `open interest structure <https://docs.ccxt.com/?id=open-interest-structure>`
"""
symbol = self.symbol(symbol)
self.load_markets()
ois = self.fetch_open_interests([symbol], params)
return ois[symbol]
def parse_open_interest(self, interest, market: Market = None):
#
# {
# "funding": "0.00010529",
# "mark": "1.084819",
# "mid": "1.08615",
# "next_funding": "0.00011096",
# "open_interest": "3634796",
# "oracle": "1.084524",
# "symbol": "XPL",
# "timestamp": 1759222967974,
# "volume_24h": "20896698.0672",
# "yesterday_price": "1.3412"
# }
#
marketId = self.safe_string(interest, 'symbol')
symbol = None
if marketId is not None:
market = self.safe_market(marketId, market)
symbol = market['symbol']
interestValue = None
markPrice = self.safe_string(interest, 'mark')
openInterest = self.safe_string(interest, 'open_interest')
if (openInterest is not None) and (markPrice is not None):
interestValue = Precise.string_mul(openInterest, markPrice)
timestamp = self.safe_integer(interest, 'timestamp')
return self.safe_open_interest({
'symbol': self.safe_symbol(symbol),
'openInterestAmount': self.parse_number(openInterest),
'openInterestValue': self.parse_number(interestValue),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'info': interest,
}, market)
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
"""
fetch the history of changes, actions done by the user or operations that altered the balance of the user
https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-balance-history
:param str [code]: unified currency code
:param int [since]: timestamp in ms of the earliest ledger entry
:param int [limit]: max number of ledger entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:param str [params.cursor]: pagination cursor from prev request(manual use)
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns dict: a `ledger structure <https://docs.ccxt.com/?id=ledger-entry-structure>`
"""
self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate', False)
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchLedger', params)
defaultLimit = 100 # Default max limit
if paginate:
return self.fetch_paginated_call_cursor('fetchLedger', code, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
request: dict = {
'account': userAddress,
}
if limit is not None:
request['limit'] = limit
response = self.publicGetAccountBalanceHistory(self.extend(request, params))
# {
# "success": True,
# "data": [
# {
# "amount": "100.000000",
# "balance": "1200.000000",
# "pending_balance": "0.000000",
# "event_type": "deposit",
# "created_at": 1716200000000
# }
# ...
# ],
# "next_cursor": "11114Lz77",
# "has_more": True
# }
data = self.add_pagination_cursor_to_result(response)
return self.parse_ledger(data, None, since, limit)
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
#
# {
# "amount": "100.000000",
# "balance": "1200.000000",
# "pending_balance": "0.000000",
# "event_type": "deposit",
# "created_at": 1716200000000
# }
#
timestamp = self.safe_integer(item, 'created_at')
type = self.safe_string(item, 'event_type')
amount = self.safe_string(item, 'amount')
balance = self.safe_string(item, 'balance')
return self.safe_ledger_entry({
'info': item,
'id': None,
'direction': None,
'account': None,
'referenceAccount': None,
'referenceId': None,
'type': self.parse_ledger_entry_type(type),
'currency': None,
'amount': self.parse_number(amount),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'before': None,
'after': self.parse_number(balance),
'status': None,
'fee': None,
}, currency)
def parse_ledger_entry_type(self, type):
ledgerType: dict = {
'subaccount_transfer': 'transfer',
'deposit': 'transaction',
'deposit_release': 'transaction',
'withdraw': 'transaction',
'trade': 'trade',
'market_liquidation': 'trade',
'backstop_liquidation': 'trade',
'adl_liquidation': 'trade',
'funding': 'funding',
'fee': 'fee',
'rebate': 'rebate',
'cashback': 'cashback',
'referral': 'referral',
'airdrop': 'airdrop',
'payout': 'payout',
}
return self.safe_string(ledgerType, type, type)
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch the history of funding payments paid and received on self account
:param str [symbol]: unified market symbol
:param int [since]: the earliest time in ms to fetch funding history for
:param int [limit]: the maximum number of funding history structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.account]: will default to walletAddress if not provided
:param str [params.cursor]: pagination cursor from prev request
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns dict: a `funding history structure <https://docs.ccxt.com/?id=funding-history-structure>`
"""
self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate', False)
userAddress = None
userAddress, params = self.handle_origin_and_single_address('fetchFundingHistory', params)
request: dict = {
'account': userAddress,
}
if limit is not None:
request['limit'] = limit
defaultLimit = 100
if paginate:
return self.fetch_paginated_call_cursor('fetchFundingHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
response = self.publicGetFundingHistory(self.extend(request, params))
# {
# "success": True,
# "data": [
# {
# "history_id": 2287920,
# "symbol": "PUMP",
# "side": "ask",
# "amount": "39033804",
# "payout": "2.617479",
# "rate": "0.0000125",
# "created_at": 1759222804122
# },
# ...
# ],
# "next_cursor": "11114Lz77",
# "has_more": True
# }
data = self.add_pagination_cursor_to_result(response)
return self.parse_incomes(data, market, since, limit)
def parse_income(self, income, market: Market = None):
#
# {
# "history_id": 2287920,
# "symbol": "PUMP",
# "side": "ask",
# "amount": "39033804",
# "payout": "2.617479",
# "rate": "0.0000125",
# "created_at": 1759222804122
# }
#
id = self.safe_string(income, 'history_id')
timestamp = self.safe_integer(income, 'created_at')
marketId = self.safe_string(income, 'symbol')
market = self.safe_market(marketId, market)
symbol = market['symbol']
amount = self.safe_string(income, 'amount')
code = self.safe_currency_code('USDC')
rate = self.safe_number(income, 'rate')
return {
'info': income,
'symbol': symbol,
'code': code,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'id': id,
'amount': self.parse_number(amount),
'rate': rate,
}
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
"""
transfer currency internally between wallets on the same account
https://docs.pacifica.fi/api-documentation/api/rest-api/subaccounts/subaccount-fund-transfer
:param str code: unified currency code
:param float amount: amount to transfer
:param str fromAccount: account to transfer from *spot, swap*
:param str toAccount: account to transfer to *swap, spot or address*
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.expiryWindow]: time to live in milliseconds
:returns dict: a `transfer structure <https://docs.ccxt.com/?id=transfer-structure>`
"""
operationType = 'transfer_funds'
sigPayload = {
'to_account': toAccount,
'amount': amount,
}
request = self.post_action_request(operationType, sigPayload, params)
params = self.omit(params, ['expiryWindow'])
response = self.privatePostAccountSubaccountTransfer(self.extend(request, params))
#
# {
# "success": True,
# "data": {
# "success": True,
# "error": null
# },
# "error": null,
# "code": null
# }
#
data = self.safe_dict(response, 'data', {})
return self.parse_transfer(data)
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
#
# {
# "success": True,
# "data": {
# "success": True,
# "error": null
# },
# "error": null,
# "code": null
# }
#
return {
'info': transfer,
'id': None,
'timestamp': None,
'datetime': None,
'currency': None,
'amount': None,
'fromAccount': None,
'toAccount': None,
'status': 'ok',
}
def create_sub_account(self, name: str, params={}):
"""
creates a sub-account under the main account
:param str name: unused argument
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.expiryWindow]: time to live in milliseconds
:param str [params.subAccountAddress]: - The public key(address) of the sub-account to use for creation
:param str [params.subAccountPrivateKey]: - The private key of the sub-account to use for creation
:returns dict: a response object
"""
finalHeaders = {}
agentAddress = None
agentAddress, params = self.handle_option('createSubAccount', 'agentAddress', None)
originAddress = None
originAddress, params = self.handle_origin_and_single_address('createSubAccount', params)
if originAddress is None:
raise ArgumentsRequired(self.id + ' createSubAccount() requires "originAddress" in params or "walletAddress" in requiredCredentials')
if agentAddress is not None:
finalHeaders['agent_wallet'] = agentAddress
subAccountAddress = None
subAccountAddress, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountAddress')
subAccountPrivateKey = None
subAccountPrivateKey, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountPrivateKey')
if subAccountAddress is None:
raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountAddress"!')
if subAccountPrivateKey is None:
raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountPrivateKey"!')
timestamp = self.milliseconds()
expiryWindow = None
expiryWindow, params = self.handle_option_and_params_2(params, 'createSubAccount', 'expiryWindow', 'expiry_window', 5000)
subaccountSignatureHeader = {
'timestamp': timestamp,
'expiry_window': expiryWindow,
'type': 'subaccount_initiate',
}
subSigPayload = {
'account': originAddress,
}
subaccountSignature = self.sign_message(subaccountSignatureHeader, subSigPayload, subAccountPrivateKey)
mainSignatureHeader = {
'timestamp': timestamp,
'expiry_window': expiryWindow,
'type': 'subaccount_confirm',
}
mainSigPayload = {
'signature': subaccountSignature,
}
main_signature = self.sign_message(mainSignatureHeader, mainSigPayload, self.privateKey)
finalHeaders['main_account'] = originAddress
finalHeaders['subaccount'] = subAccountAddress
finalHeaders['sub_signature'] = subaccountSignature
finalHeaders['main_signature'] = main_signature
finalHeaders['timestamp'] = timestamp
finalHeaders['expiry_window'] = expiryWindow
request = finalHeaders
response = self.privatePostAccountSubaccountCreate(request)
#
# {
# "success": True,
# "data": null,
# "error": null,
# "code": null,
# }
#
return response
def bind_agent_wallet(self, agentAddress: str, params={}):
operationType = 'bind_agent_wallet'
sigPayload = {
'agent_wallet': agentAddress,
}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAgentBind(self.extend(request, params))
def create_api_key(self, params={}):
operationType = 'create_api_key'
sigPayload = {}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAccountApiKeysCreate(self.extend(request, params))
def revoke_api_key(self, apiKey: str, params={}):
operationType = 'revoke_api_key'
sigPayload = {
'api_key': apiKey,
}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAccountApiKeysRevoke(self.extend(request, params))
def fetch_api_keys(self, params={}):
operationType = 'list_api_keys'
sigPayload = {}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAccountApiKeys(self.extend(request, params))
def approve_builder_code(self, builderCode: str, maxFeeRate: str, params={}):
operationType = 'approve_builder_code'
sigPayload = {
'builder_code': builderCode,
'max_fee_rate': maxFeeRate,
}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAccountBuilderCodesApprove(self.extend(request, params))
def fetch_builder_approvals(self, address: str):
request = {
'account': address,
}
return self.publicGetAccountBuilderCodesApprovals(self.extend(request))
def revoke_builder_code(self, builderCode: str, params={}):
operationType = 'revoke_builder_code'
sigPayload = {
'builder_code': builderCode,
}
request = self.post_action_request(operationType, sigPayload, params)
return self.privatePostAccountBuilderCodesRevoke(self.extend(request, params))
def handle_origin_and_single_address(self, methodName: str, params: dict):
address = None
address, params = self.handle_param_string_2(params, 'account', 'address', None) # self is for get endpoints that accept account or address
if address is not None:
return [address, params]
address1 = self.walletAddress
if address1 is not None:
return [address1, params]
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires address either as "exchange.walletAddress = ..." or or "address" in params')
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
if response is None:
return None # fallback to default error handler
#
# {"success":false,"data":null,"error":"Beta access required. Signer must redeem a valid beta code.","code":403}
# {"success":false,"data":null,"error":"Agent not authorized for account","code":400}
# {"success":false,"data":null,"error":"Internal server error","code":500}
#
inCode = self.safe_integer(response, 'code') # actually if all ok -> code = None or code = 200
message = self.safe_string(response, 'error')
error = None
if inCode is None or inCode == 200:
error = False
else:
error = True
nonEmptyMessage = ((message is not None) and (message != ''))
if error or nonEmptyMessage:
feedback = self.id + ' ' + body
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback) # Try deeper catch first
self.throw_exactly_matched_exception(self.exceptions['exact'], inCode, feedback)
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
raise ExchangeError(feedback) # unknown message
return None
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
isTestnet = self.isSandboxModeEnabled
urlKey = 'test' if (isTestnet) else 'api'
host = self.implode_hostname(self.urls[urlKey][api])
url = host + '/api/' + self.version + '/' + self.implode_params(path, params)
params = self.omit(params, self.extract_params(path))
paramsLen = params
headers = {
'Content-Type': 'application/json',
}
if method == 'GET' and paramsLen:
url += '?' + self.urlencode(params)
headers['Accept'] = '*/*'
if method == 'POST':
body = self.json(params)
if self.handle_option('sign', 'apiKey', None) is not None:
headers['PF-API-KEY'] = self.options['apiKey']
return {'url': url, 'method': method, 'body': body, 'headers': headers}
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
cost = self.safe_string(config, 'cost', '1')
costNumber = self.parse_number(cost)
# 1 is normal POST/GET, 0.5 is cancels, 3-12 is heavy GET
if costNumber > 1:
if self.handle_option(method, 'apiKey', None) is not None:
costWithKey = self.handle_option(
method,
'maxCostHugeWithApiKey',
3
)
return costWithKey
return costNumber
def sort_json_keys(self, value: Any) -> Any:
if isinstance(value, dict):
result = {}
keys = list(value.keys())
sortedKeys = self.sort(keys)
for i in range(0, len(sortedKeys)):
key = sortedKeys[i]
result[key] = self.sort_json_keys(value[key])
return result
elif isinstance(value, list):
result = []
for i in range(0, len(value)):
result.append(self.sort_json_keys(value[i]))
return result
else:
return value
def prepare_message(self, header: dict, payload: dict) -> str:
if header['type'] is None or header['timestamp'] is None or header['expiry_window'] is None:
raise ArgumentsRequired(self.id + ' prepareMessage() requires type, timestamp, expiry_window in header')
data = self.extend(header, {'data': payload})
sorted = self.sort_json_keys(data)
return self.json(sorted)
def sign_message(self, header: dict, payload: dict, privateKey: str) -> str:
message = self.prepare_message(header, payload)
messageBytes = self.encode(message)
secretBytes = self.base58_to_binary(privateKey)
seed = self.array_slice(secretBytes, 0, 32)
signatureBase64 = self.eddsa(messageBytes, seed, 'ed25519')
signatureBinary = self.base64_to_binary(signatureBase64)
signatureBase58 = self.binary_to_base58(signatureBinary)
return signatureBase58
def post_action_request(self, operationType: Str, sigPayload: dict, params: dict) -> dict:
self.check_required_credentials() # check credentials every post action
if operationType == 'None':
raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "operationType"')
if not self.isSandboxModeEnabled: # At self stage, building codes are mostly only on the mainnet.
useBuilder = self.handle_option('postActionRequest', 'builderFee', True)
builderCode = None
if useBuilder:
builderCode = self.handle_option('postActionRequest', 'builderCode')
if builderCode is not None:
isOperationSupportBuilder = self.safe_bool(self.options['builderSupportOperations'], operationType, False)
if isOperationSupportBuilder:
sigPayload['builder_code'] = builderCode
expiryWindow = None
expiryWindow, params = self.handle_option_and_params_2(params, 'postActionRequest', 'expiryWindow', 'expiry_window', 5000)
timestamp = self.safe_integer(params, 'timestamp', self.milliseconds())
signatureHeader = {
'timestamp': timestamp,
'expiry_window': expiryWindow,
'type': operationType,
}
signature = self.sign_message(signatureHeader, sigPayload, self.privateKey)
finalHeaders = {}
agentAddress = None
agentAddress, params = self.handle_option_and_params(params, 'postActionRequest', 'agentAddress')
originAddress = None
originAddress, params = self.handle_origin_and_single_address('postActionRequest', params)
if originAddress is None:
raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "originAddress" in params or "walletAddress" in requiredCredentials')
finalHeaders['account'] = originAddress
if agentAddress is not None:
finalHeaders['agent_wallet'] = agentAddress
finalHeaders['signature'] = signature
finalHeaders['timestamp'] = self.safe_integer(signatureHeader, 'timestamp')
finalHeaders['expiry_window'] = self.safe_integer(signatureHeader, 'expiry_window')
request = self.extend(finalHeaders, sigPayload)
return request