Skip to content

Commit 0c75b52

Browse files
committed
[Exchanges] add coingecko, dexscreener, up changenow
1 parent 7f0f09a commit 0c75b52

7 files changed

Lines changed: 258 additions & 34 deletions

File tree

python/ccxt/async_support/changenow.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ccxt.base.errors import AuthenticationError
1212
from ccxt.base.errors import ArgumentsRequired
1313
from ccxt.base.errors import BadRequest
14+
from ccxt.base.errors import InvalidOrder
1415
from ccxt.base.errors import OrderNotFound
1516
from ccxt.base.decimal_to_precision import TICK_SIZE
1617

@@ -154,6 +155,8 @@ def describe(self) -> Any:
154155
},
155156
},
156157
'options': {
158+
'defaultAPIKey': '',
159+
'forceDefaultAPIKey': False,
157160
'statusMapping': {
158161
'new': 'open',
159162
'waiting': 'open',
@@ -169,6 +172,31 @@ def describe(self) -> Any:
169172
},
170173
})
171174

175+
def resolve_api_key(self) -> Str:
176+
if self.safe_bool(self.options, 'forceDefaultAPIKey', False):
177+
return self.safe_string(self.options, 'defaultAPIKey')
178+
if self.apiKey:
179+
return self.apiKey
180+
return self.safe_string(self.options, 'defaultAPIKey')
181+
182+
def check_required_credentials(self, error=True):
183+
keys = list(self.requiredCredentials.keys())
184+
for credentialIndex in range(0, len(keys)):
185+
key = keys[credentialIndex]
186+
if self.requiredCredentials[key]:
187+
if key == 'apiKey':
188+
if not self.resolve_api_key():
189+
if error:
190+
raise AuthenticationError(self.id + ' requires "' + key + '" credential')
191+
else:
192+
return False
193+
elif not getattr(self, key):
194+
if error:
195+
raise AuthenticationError(self.id + ' requires "' + key + '" credential')
196+
else:
197+
return False
198+
return True
199+
172200
async def fetch_currencies(self, params={}) -> Currencies:
173201
"""
174202
fetches all available currencies on ChangeNOW
@@ -330,7 +358,7 @@ async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
330358
response = None
331359
if fixedRate:
332360
# v1 fixed-rate estimate requires api_key query parameter
333-
request['api_key'] = self.apiKey
361+
request['api_key'] = self.resolve_api_key()
334362
response = await self.publicGetExchangeAmountFixedRateAmountFromTo(self.extend(request, params))
335363
else:
336364
response = await self.publicGetExchangeAmountAmountFromTo(self.extend(request, params))
@@ -389,9 +417,9 @@ async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amou
389417
:param float amount: amount of the base currency to send
390418
:param float [price]: not used for ChangeNOW standard flow
391419
:param dict [params]: extra parameters specific to the exchange API endpoint
392-
:param str params['address']: destination address for the quote currency(required)
420+
:param str params['address_to']: destination address for the quote currency(required)
393421
:param str [params.extraId]: extra id or memo for the destination address(e.g. for XRP, XLM)
394-
:param str [params.refundAddress]: refund address for the base currency
422+
:param str [params.refund_address]: refund address for the base currency
395423
:param str [params.refundExtraId]: extra id or memo for the refund address
396424
:param str [params.rateId]: rate id from fixed-rate estimate(required for fixed-rate)
397425
:param str [params.contactEmail]: contact email for notifications
@@ -402,20 +430,20 @@ async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amou
402430
market = self.market(symbol)
403431
baseId = self.safe_string(market, 'baseId')
404432
quoteId = self.safe_string(market, 'quoteId')
405-
address = self.safe_string(params, 'address')
406-
if address is None:
407-
raise ArgumentsRequired(self.id + ' createOrder() requires params.address – the destination address for the received currency')
433+
addressTo = self.safe_string(params, 'address_to')
434+
if addressTo is None:
435+
raise ArgumentsRequired(self.id + ' createOrder() requires params.address_to – the destination address for the received currency')
408436
request: dict = {
409-
'apiKey': self.apiKey,
437+
'apiKey': self.resolve_api_key(),
410438
'from': baseId,
411439
'to': quoteId,
412440
'amount': self.number_to_string(amount),
413-
'address': address,
441+
'address': addressTo,
414442
}
415443
extraId = self.safe_string(params, 'extraId')
416444
if extraId is not None:
417445
request['extraId'] = extraId
418-
refundAddress = self.safe_string(params, 'refundAddress')
446+
refundAddress = self.safe_string(params, 'refund_address')
419447
if refundAddress is not None:
420448
request['refundAddress'] = refundAddress
421449
refundExtraId = self.safe_string(params, 'refundExtraId')
@@ -427,7 +455,7 @@ async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amou
427455
contactEmail = self.safe_string(params, 'contactEmail')
428456
if contactEmail is not None:
429457
request['contactEmail'] = contactEmail
430-
params = self.omit(params, ['address', 'extraId', 'refundAddress', 'refundExtraId', 'rateId', 'contactEmail'])
458+
params = self.omit(params, ['address_to', 'extraId', 'refund_address', 'refundExtraId', 'rateId', 'contactEmail'])
431459
response = await self.privatePostTransactionsApiKey(self.extend(request, params))
432460
#
433461
# {
@@ -456,7 +484,7 @@ async def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
456484
self.check_required_credentials()
457485
request: dict = {
458486
'id': id,
459-
'apiKey': self.apiKey,
487+
'apiKey': self.resolve_api_key(),
460488
}
461489
response = await self.privateGetTransactionsIdApiKey(self.extend(request, params))
462490
#
@@ -577,6 +605,7 @@ def handle_errors(self, httpCode: int, reason: str, url: str, method: str, heade
577605
# {"error": "pair_is_inactive", "message": "This pair is currently unavailable"}
578606
# {"error": "not_valid_params", "message": "Invalid request parameters"}
579607
# {"error": "deposit_too_small", "message": "Deposit amount is less than minimum"}
608+
# {"error": "out_of_range", "message": "Amount is less then minimal: 0.0223925 XMR"}
580609
# {"message": "Unauthorized"}
581610
#
582611
error = self.safe_string(response, 'error')
@@ -588,6 +617,8 @@ def handle_errors(self, httpCode: int, reason: str, url: str, method: str, heade
588617
raise AuthenticationError(self.id + ' ' + body)
589618
if error == 'not_found':
590619
raise OrderNotFound(self.id + ' ' + body)
620+
if error == 'deposit_too_small' or error == 'out_of_range':
621+
raise InvalidOrder(self.id + ' ' + body)
591622
raise ExchangeError(self.id + ' ' + body)
592623
if message is not None and httpCode >= 400:
593624
if httpCode == 401 or httpCode == 403:

python/ccxt/async_support/ob_changenow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def describe(self) -> Any:
2626
'option': False,
2727
},
2828
'options': {
29+
'defaultAPIKey': '5f7dc0622f06ed2256261edd51a062c551cb93b799909f02dcaf695c40493c6c',
2930
'octobot': {
3031
'supportedElements': {
3132
'spot': {
@@ -38,6 +39,7 @@ def describe(self) -> Any:
3839
},
3940
},
4041
'fixMarketStatus': True,
42+
'removeMarketStatusPriceLimits': True,
4143
'supportFetchingCancelledOrders': False,
4244
'requireClosedOrdersFromRecentTrades': False,
4345
'createOhlcvFromTickers': True,

python/ccxt/changenow.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ccxt.base.errors import AuthenticationError
1212
from ccxt.base.errors import ArgumentsRequired
1313
from ccxt.base.errors import BadRequest
14+
from ccxt.base.errors import InvalidOrder
1415
from ccxt.base.errors import OrderNotFound
1516
from ccxt.base.decimal_to_precision import TICK_SIZE
1617

@@ -154,6 +155,8 @@ def describe(self) -> Any:
154155
},
155156
},
156157
'options': {
158+
'defaultAPIKey': '',
159+
'forceDefaultAPIKey': False,
157160
'statusMapping': {
158161
'new': 'open',
159162
'waiting': 'open',
@@ -169,6 +172,31 @@ def describe(self) -> Any:
169172
},
170173
})
171174

175+
def resolve_api_key(self) -> Str:
176+
if self.safe_bool(self.options, 'forceDefaultAPIKey', False):
177+
return self.safe_string(self.options, 'defaultAPIKey')
178+
if self.apiKey:
179+
return self.apiKey
180+
return self.safe_string(self.options, 'defaultAPIKey')
181+
182+
def check_required_credentials(self, error=True):
183+
keys = list(self.requiredCredentials.keys())
184+
for credentialIndex in range(0, len(keys)):
185+
key = keys[credentialIndex]
186+
if self.requiredCredentials[key]:
187+
if key == 'apiKey':
188+
if not self.resolve_api_key():
189+
if error:
190+
raise AuthenticationError(self.id + ' requires "' + key + '" credential')
191+
else:
192+
return False
193+
elif not getattr(self, key):
194+
if error:
195+
raise AuthenticationError(self.id + ' requires "' + key + '" credential')
196+
else:
197+
return False
198+
return True
199+
172200
def fetch_currencies(self, params={}) -> Currencies:
173201
"""
174202
fetches all available currencies on ChangeNOW
@@ -330,7 +358,7 @@ def fetch_ticker(self, symbol: str, params={}) -> Ticker:
330358
response = None
331359
if fixedRate:
332360
# v1 fixed-rate estimate requires api_key query parameter
333-
request['api_key'] = self.apiKey
361+
request['api_key'] = self.resolve_api_key()
334362
response = self.publicGetExchangeAmountFixedRateAmountFromTo(self.extend(request, params))
335363
else:
336364
response = self.publicGetExchangeAmountAmountFromTo(self.extend(request, params))
@@ -389,9 +417,9 @@ def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: fl
389417
:param float amount: amount of the base currency to send
390418
:param float [price]: not used for ChangeNOW standard flow
391419
:param dict [params]: extra parameters specific to the exchange API endpoint
392-
:param str params['address']: destination address for the quote currency(required)
420+
:param str params['address_to']: destination address for the quote currency(required)
393421
:param str [params.extraId]: extra id or memo for the destination address(e.g. for XRP, XLM)
394-
:param str [params.refundAddress]: refund address for the base currency
422+
:param str [params.refund_address]: refund address for the base currency
395423
:param str [params.refundExtraId]: extra id or memo for the refund address
396424
:param str [params.rateId]: rate id from fixed-rate estimate(required for fixed-rate)
397425
:param str [params.contactEmail]: contact email for notifications
@@ -402,20 +430,20 @@ def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: fl
402430
market = self.market(symbol)
403431
baseId = self.safe_string(market, 'baseId')
404432
quoteId = self.safe_string(market, 'quoteId')
405-
address = self.safe_string(params, 'address')
406-
if address is None:
407-
raise ArgumentsRequired(self.id + ' createOrder() requires params.address – the destination address for the received currency')
433+
addressTo = self.safe_string(params, 'address_to')
434+
if addressTo is None:
435+
raise ArgumentsRequired(self.id + ' createOrder() requires params.address_to – the destination address for the received currency')
408436
request: dict = {
409-
'apiKey': self.apiKey,
437+
'apiKey': self.resolve_api_key(),
410438
'from': baseId,
411439
'to': quoteId,
412440
'amount': self.number_to_string(amount),
413-
'address': address,
441+
'address': addressTo,
414442
}
415443
extraId = self.safe_string(params, 'extraId')
416444
if extraId is not None:
417445
request['extraId'] = extraId
418-
refundAddress = self.safe_string(params, 'refundAddress')
446+
refundAddress = self.safe_string(params, 'refund_address')
419447
if refundAddress is not None:
420448
request['refundAddress'] = refundAddress
421449
refundExtraId = self.safe_string(params, 'refundExtraId')
@@ -427,7 +455,7 @@ def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: fl
427455
contactEmail = self.safe_string(params, 'contactEmail')
428456
if contactEmail is not None:
429457
request['contactEmail'] = contactEmail
430-
params = self.omit(params, ['address', 'extraId', 'refundAddress', 'refundExtraId', 'rateId', 'contactEmail'])
458+
params = self.omit(params, ['address_to', 'extraId', 'refund_address', 'refundExtraId', 'rateId', 'contactEmail'])
431459
response = self.privatePostTransactionsApiKey(self.extend(request, params))
432460
#
433461
# {
@@ -456,7 +484,7 @@ def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
456484
self.check_required_credentials()
457485
request: dict = {
458486
'id': id,
459-
'apiKey': self.apiKey,
487+
'apiKey': self.resolve_api_key(),
460488
}
461489
response = self.privateGetTransactionsIdApiKey(self.extend(request, params))
462490
#
@@ -577,6 +605,7 @@ def handle_errors(self, httpCode: int, reason: str, url: str, method: str, heade
577605
# {"error": "pair_is_inactive", "message": "This pair is currently unavailable"}
578606
# {"error": "not_valid_params", "message": "Invalid request parameters"}
579607
# {"error": "deposit_too_small", "message": "Deposit amount is less than minimum"}
608+
# {"error": "out_of_range", "message": "Amount is less then minimal: 0.0223925 XMR"}
580609
# {"message": "Unauthorized"}
581610
#
582611
error = self.safe_string(response, 'error')
@@ -588,6 +617,8 @@ def handle_errors(self, httpCode: int, reason: str, url: str, method: str, heade
588617
raise AuthenticationError(self.id + ' ' + body)
589618
if error == 'not_found':
590619
raise OrderNotFound(self.id + ' ' + body)
620+
if error == 'deposit_too_small' or error == 'out_of_range':
621+
raise InvalidOrder(self.id + ' ' + body)
591622
raise ExchangeError(self.id + ' ' + body)
592623
if message is not None and httpCode >= 400:
593624
if httpCode == 401 or httpCode == 403:

python/ccxt/ob_changenow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def describe(self) -> Any:
2626
'option': False,
2727
},
2828
'options': {
29+
'defaultAPIKey': '5f7dc0622f06ed2256261edd51a062c551cb93b799909f02dcaf695c40493c6c',
2930
'octobot': {
3031
'supportedElements': {
3132
'spot': {
@@ -38,6 +39,7 @@ def describe(self) -> Any:
3839
},
3940
},
4041
'fixMarketStatus': True,
42+
'removeMarketStatusPriceLimits': True,
4143
'supportFetchingCancelledOrders': False,
4244
'requireClosedOrdersFromRecentTrades': False,
4345
'createOhlcvFromTickers': True,

0 commit comments

Comments
 (0)