diff --git a/cs/ccxt/api/geniusyield.cs b/cs/ccxt/api/geniusyield.cs new file mode 100644 index 000000000000..3e83fc45c879 --- /dev/null +++ b/cs/ccxt/api/geniusyield.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------- + +// 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 + +// ------------------------------------------------------------------------------- + +namespace ccxt; + +public partial class geniusyield : Exchange +{ + public geniusyield (object args = null): base(args) {} + + public async Task privateGetBalancesAddress (object parameters = null) + { + return await this.callAsync ("privateGetBalancesAddress",parameters); + } + + public async Task privateGetMarkets (object parameters = null) + { + return await this.callAsync ("privateGetMarkets",parameters); + } + + public async Task privateGetTradingFees (object parameters = null) + { + return await this.callAsync ("privateGetTradingFees",parameters); + } + + public async Task privateGetSettings (object parameters = null) + { + return await this.callAsync ("privateGetSettings",parameters); + } + +} \ No newline at end of file diff --git a/cs/ccxt/exchanges/geniusyield.cs b/cs/ccxt/exchanges/geniusyield.cs new file mode 100644 index 000000000000..500187b7c110 --- /dev/null +++ b/cs/ccxt/exchanges/geniusyield.cs @@ -0,0 +1,2036 @@ +namespace ccxt; + +// 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 + +public partial class geniusyield : Exchange +{ + public override object describe() + { + return this.deepExtend(base.describe(), new Dictionary() { + { "id", "geniusyield" }, + { "name", "Genius Yield" }, + { "countries", new List() {"CH"} }, + { "rateLimit", 1000 }, + { "version", "v0" }, + { "pro", false }, + { "dex", true }, + { "certified", false }, + { "requiresWeb3", true }, + { "has", new Dictionary() { + { "CORS", null }, + { "spot", true }, + { "margin", false }, + { "swap", false }, + { "future", false }, + { "option", false }, + { "addMargin", false }, + { "cancelAllOrders", true }, + { "cancelOrder", true }, + { "cancelOrders", false }, + { "closeAllPositions", false }, + { "closePosition", false }, + { "createDepositAddress", false }, + { "createOrder", true }, + { "createReduceOnlyOrder", false }, + { "createStopLimitOrder", true }, + { "createStopMarketOrder", true }, + { "createStopOrder", true }, + { "fetchBalance", true }, + { "fetchBorrowRateHistories", false }, + { "fetchBorrowRateHistory", false }, + { "fetchClosedOrders", true }, + { "fetchCrossBorrowRate", false }, + { "fetchCrossBorrowRates", false }, + { "fetchCurrencies", true }, + { "fetchDeposit", true }, + { "fetchDepositAddress", true }, + { "fetchDepositAddresses", false }, + { "fetchDepositAddressesByNetwork", false }, + { "fetchDeposits", true }, + { "fetchFundingHistory", false }, + { "fetchFundingRate", false }, + { "fetchFundingRateHistory", false }, + { "fetchFundingRates", false }, + { "fetchIndexOHLCV", false }, + { "fetchIsolatedBorrowRate", false }, + { "fetchIsolatedBorrowRates", false }, + { "fetchLeverage", false }, + { "fetchLeverageTiers", false }, + { "fetchMarginMode", false }, + { "fetchMarkets", true }, + { "fetchMarkOHLCV", false }, + { "fetchMyTrades", true }, + { "fetchOHLCV", true }, + { "fetchOpenInterestHistory", false }, + { "fetchOpenOrders", true }, + { "fetchOrder", true }, + { "fetchOrderBook", true }, + { "fetchOrders", false }, + { "fetchPosition", false }, + { "fetchPositionHistory", false }, + { "fetchPositionMode", false }, + { "fetchPositions", false }, + { "fetchPositionsForSymbol", false }, + { "fetchPositionsHistory", false }, + { "fetchPositionsRisk", false }, + { "fetchPremiumIndexOHLCV", false }, + { "fetchStatus", true }, + { "fetchTicker", true }, + { "fetchTickers", true }, + { "fetchTime", true }, + { "fetchTrades", true }, + { "fetchTradingFee", false }, + { "fetchTradingFees", true }, + { "fetchTransactions", false }, + { "fetchWithdrawal", true }, + { "fetchWithdrawals", true }, + { "reduceMargin", false }, + { "sandbox", true }, + { "setLeverage", false }, + { "setMarginMode", false }, + { "setPositionMode", false }, + { "transfer", false }, + { "withdraw", true }, + } }, + { "timeframes", new Dictionary() { + { "1m", "1m" }, + { "5m", "5m" }, + { "15m", "15m" }, + { "30m", "30m" }, + { "1h", "1h" }, + { "6h", "6h" }, + { "1d", "1d" }, + } }, + { "urls", new Dictionary() { + { "test", new Dictionary() { + { "MATIC", "https://api-sandbox-matic.geniusyield.io" }, + } }, + { "logo", "https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg" }, + { "api", new Dictionary() { + { "MATIC", "https://api-matic.geniusyield.io" }, + } }, + { "www", "https://geniusyield.io" }, + { "doc", new List() {"https://api-docs-v3.geniusyield.io/"} }, + } }, + { "api", new Dictionary() { + { "public", new Dictionary() { + { "get", new Dictionary() { + { "ping", 1 }, + { "time", 1 }, + { "exchange", 1 }, + { "assets", 1 }, + { "markets", 1 }, + { "tickers", 1 }, + { "candles", 1 }, + { "trades", 1 }, + { "orderbook", 1 }, + } }, + } }, + { "private", new Dictionary() { + { "get", new Dictionary() { + { "user", 1 }, + { "wallets", 1 }, + { "balances", 1 }, + { "orders", 0.1 }, + { "fills", 0.1 }, + { "deposits", 1 }, + { "withdrawals", 1 }, + { "wsToken", 1 }, + } }, + { "post", new Dictionary() { + { "wallets", 1 }, + { "orders", 0.1 }, + { "orders/test", 0.1 }, + { "withdrawals", 1 }, + } }, + { "delete", new Dictionary() { + { "orders", 0.1 }, + } }, + } }, + } }, + { "options", new Dictionary() { + { "defaultTimeInForce", "gtc" }, + { "defaultSelfTradePrevention", "cn" }, + { "network", "MATIC" }, + } }, + { "exceptions", new Dictionary() { + { "exact", new Dictionary() { + { "INVALID_ORDER_QUANTITY", typeof(InvalidOrder) }, + { "INSUFFICIENT_FUNDS", typeof(InsufficientFunds) }, + { "SERVICE_UNAVAILABLE", typeof(ExchangeNotAvailable) }, + { "EXCEEDED_RATE_LIMIT", typeof(DDoSProtection) }, + { "INVALID_PARAMETER", typeof(BadRequest) }, + { "WALLET_NOT_ASSOCIATED", typeof(InvalidAddress) }, + { "INVALID_WALLET_SIGNATURE", typeof(AuthenticationError) }, + } }, + } }, + { "requiredCredentials", new Dictionary() { + { "walletAddress", true }, + { "privateKey", true }, + { "apiKey", true }, + { "secret", true }, + } }, + { "precisionMode", TICK_SIZE }, + { "paddingMode", PAD_WITH_ZERO }, + { "commonCurrencies", new Dictionary() {} }, + }); + } + + public override object priceToPrecision(object symbol, object price) + { + // + // we override priceToPrecision to fix the following issue + // https://github.com/ccxt/ccxt/issues/13367 + // {"code":"INVALID_PARAMETER","message":"invalid value provided for request parameter \"price\": all quantities and prices must be below 100 billion, above 0, need to be provided as strings, and always require 4 decimals ending with 4 zeroes"} + // + object market = this.market(symbol); + object info = this.safeValue(market, "info", new Dictionary() {}); + object quoteAssetPrecision = this.safeInteger(info, "quoteAssetPrecision"); + price = this.decimalToPrecision(price, ROUND, getValue(getValue(market, "precision"), "price"), this.precisionMode); + return this.decimalToPrecision(price, TRUNCATE, quoteAssetPrecision, DECIMAL_PLACES, PAD_WITH_ZERO); + } + + public async override Task fetchMarkets(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchMarkets + * @description retrieves data on all markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-markets + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} an array of objects representing market data + */ + parameters ??= new Dictionary(); + object response = await this.publicGetMarkets(parameters); + // + // [ + // { + // "market": "ETH-USDC", + // "type": "hybrid", + // "status": "activeHybrid", + // "baseAsset": "ETH", + // "baseAssetPrecision": "8", + // "quoteAsset": "USDC", + // "quoteAssetPrecision": "8", + // "makerFeeRate": "0.0000", + // "takerFeeRate": "0.2500", + // "takerIdexFeeRate": "0.0500", + // "takerLiquidityProviderFeeRate": "0.2000", + // "tickSize": "0.01000000" + // }, + // ] + // + object response2 = await this.publicGetExchange(); + // + // { + // "timeZone": "UTC", + // "serverTime": "1654460599952", + // "maticDepositContractAddress": "0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6", + // "maticCustodyContractAddress": "0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468", + // "maticUsdPrice": "0.60", + // "gasPrice": "180", + // "volume24hUsd": "10015814.46", + // "totalVolumeUsd": "1589273533.28", + // "totalTrades": "1534904", + // "totalValueLockedUsd": "12041929.44", + // "geniusyieldStakingValueLockedUsd": "20133816.98", + // "geniusyieldTokenAddress": "0x9Cb74C8032b007466865f060ad2c46145d45553D", + // "geniusyieldUsdPrice": "0.07", + // "geniusyieldMarketCapUsd": "48012346.00", + // "makerFeeRate": "0.0000", + // "takerFeeRate": "0.0025", + // "takerIdexFeeRate": "0.0005", + // "takerLiquidityProviderFeeRate": "0.0020", + // "makerTradeMinimum": "10.00000000", + // "takerTradeMinimum": "1.00000000", + // "withdrawMinimum": "0.50000000", + // "liquidityAdditionMinimum": "0.50000000", + // "liquidityRemovalMinimum": "0.40000000", + // "blockConfirmationDelay": "64" + // } + // + object maker = this.safeNumber(response2, "makerFeeRate"); + object taker = this.safeNumber(response2, "takerFeeRate"); + object makerMin = this.safeString(response2, "makerTradeMinimum"); + object takerMin = this.safeString(response2, "takerTradeMinimum"); + object minCostETH = this.parseNumber(Precise.stringMin(makerMin, takerMin)); + object result = new List() {}; + for (object i = 0; isLessThan(i, getArrayLength(response)); postFixIncrement(ref i)) + { + object entry = getValue(response, i); + object marketId = this.safeString(entry, "market"); + object baseId = this.safeString(entry, "baseAsset"); + object quoteId = this.safeString(entry, "quoteAsset"); + object bs = this.safeCurrencyCode(baseId); + object quote = this.safeCurrencyCode(quoteId); + object basePrecision = this.parseNumber(this.parsePrecision(this.safeString(entry, "baseAssetPrecision"))); + object quotePrecision = this.parseNumber(this.parsePrecision(this.safeString(entry, "quoteAssetPrecision"))); + object status = this.safeString(entry, "status"); + object minCost = null; + if (isTrue(isEqual(quote, "ETH"))) + { + minCost = minCostETH; + } + ((IList)result).Add(new Dictionary() { + { "id", marketId }, + { "symbol", add(add(bs, "/"), quote) }, + { "base", bs }, + { "quote", quote }, + { "settle", null }, + { "baseId", baseId }, + { "quoteId", quoteId }, + { "settleId", null }, + { "type", "spot" }, + { "spot", true }, + { "margin", false }, + { "swap", false }, + { "future", false }, + { "option", false }, + { "active", (!isEqual(status, "inactive")) }, + { "contract", false }, + { "linear", null }, + { "inverse", null }, + { "taker", taker }, + { "maker", maker }, + { "contractSize", null }, + { "expiry", null }, + { "expiryDatetime", null }, + { "strike", null }, + { "optionType", null }, + { "precision", new Dictionary() { + { "amount", basePrecision }, + { "price", this.safeNumber(entry, "tickSize") }, + } }, + { "limits", new Dictionary() { + { "leverage", new Dictionary() { + { "min", null }, + { "max", null }, + } }, + { "amount", new Dictionary() { + { "min", basePrecision }, + { "max", null }, + } }, + { "price", new Dictionary() { + { "min", quotePrecision }, + { "max", null }, + } }, + { "cost", new Dictionary() { + { "min", minCost }, + { "max", null }, + } }, + } }, + { "created", null }, + { "info", entry }, + }); + } + return result; + } + + public async override Task fetchTicker(object symbol, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchTicker + * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market + * @see https://api-docs-v3.geniusyield.io/#get-tickers + * @param {string} symbol unified symbol of the market to fetch the ticker for + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + object market = this.market(symbol); + object request = new Dictionary() { + { "market", getValue(market, "id") }, + }; + // [ + // { + // "market": "DIL-ETH", + // "time": 1598367493008, + // "open": "0.09695361", + // "high": "0.10245881", + // "low": "0.09572507", + // "close": "0.09917079", + // "closeQuantity": "0.71320950", + // "baseVolume": "309.17380612", + // "quoteVolume": "30.57633981", + // "percentChange": "2.28", + // "numTrades": 205, + // "ask": "0.09910476", + // "bid": "0.09688340", + // "sequence": 3902 + // } + // ] + object response = await this.publicGetTickers(this.extend(request, parameters)); + object ticker = this.safeDict(response, 0); + return this.parseTicker(ticker, market); + } + + public async override Task fetchTickers(object symbols = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchTickers + * @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market + * @see https://api-docs-v3.geniusyield.io/#get-tickers + * @param {string[]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + // [ + // { + // "market": "DIL-ETH", + // "time": 1598367493008, + // "open": "0.09695361", + // "high": "0.10245881", + // "low": "0.09572507", + // "close": "0.09917079", + // "closeQuantity": "0.71320950", + // "baseVolume": "309.17380612", + // "quoteVolume": "30.57633981", + // "percentChange": "2.28", + // "numTrades": 205, + // "ask": "0.09910476", + // "bid": "0.09688340", + // "sequence": 3902 + // }, ... + // ] + object response = await this.publicGetTickers(parameters); + return this.parseTickers(response, symbols); + } + + public override object parseTicker(object ticker, object market = null) + { + // { + // "market": "DIL-ETH", + // "time": 1598367493008, + // "open": "0.09695361", + // "high": "0.10245881", + // "low": "0.09572507", + // "close": "0.09917079", + // "closeQuantity": "0.71320950", + // "baseVolume": "309.17380612", + // "quoteVolume": "30.57633981", + // "percentChange": "2.28", + // "numTrades": 205, + // "ask": "0.09910476", + // "bid": "0.09688340", + // "sequence": 3902 + // } + object marketId = this.safeString(ticker, "market"); + market = this.safeMarket(marketId, market, "-"); + object symbol = getValue(market, "symbol"); + object timestamp = this.safeInteger(ticker, "time"); + object close = this.safeString(ticker, "close"); + return this.safeTicker(new Dictionary() { + { "symbol", symbol }, + { "timestamp", timestamp }, + { "datetime", this.iso8601(timestamp) }, + { "high", this.safeString(ticker, "high") }, + { "low", this.safeString(ticker, "low") }, + { "bid", this.safeString(ticker, "bid") }, + { "bidVolume", null }, + { "ask", this.safeString(ticker, "ask") }, + { "askVolume", null }, + { "vwap", null }, + { "open", this.safeString(ticker, "open") }, + { "close", close }, + { "last", close }, + { "previousClose", null }, + { "change", null }, + { "percentage", this.safeString(ticker, "percentChange") }, + { "average", null }, + { "baseVolume", this.safeString(ticker, "baseVolume") }, + { "quoteVolume", this.safeString(ticker, "quoteVolume") }, + { "info", ticker }, + }, market); + } + + public async override Task fetchOHLCV(object symbol, object timeframe = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchOHLCV + * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market + * @see https://api-docs-v3.geniusyield.io/#get-candles + * @param {string} symbol unified symbol of the market to fetch OHLCV data for + * @param {string} timeframe the length of time each candle represents + * @param {int} [since] timestamp in ms of the earliest candle to fetch + * @param {int} [limit] the maximum amount of candles to fetch + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume + */ + timeframe ??= "1m"; + parameters ??= new Dictionary(); + await this.loadMarkets(); + object market = this.market(symbol); + object request = new Dictionary() { + { "market", getValue(market, "id") }, + { "interval", timeframe }, + }; + if (isTrue(!isEqual(since, null))) + { + ((IDictionary)request)["start"] = since; + } + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = mathMin(limit, 1000); + } + object response = await this.publicGetCandles(this.extend(request, parameters)); + if (isTrue(((response is IList) || (response.GetType().IsGenericType && response.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>)))))) + { + // [ + // { + // "start": 1598345580000, + // "open": "0.09771286", + // "high": "0.09771286", + // "low": "0.09771286", + // "close": "0.09771286", + // "volume": "1.45340410", + // "sequence": 3853 + // }, ... + // ] + return this.parseOHLCVs(response, market, timeframe, since, limit); + } else + { + // {"nextTime":1595536440000} + return new List() {}; + } + } + + public override object parseOHLCV(object ohlcv, object market = null) + { + // { + // "start": 1598345580000, + // "open": "0.09771286", + // "high": "0.09771286", + // "low": "0.09771286", + // "close": "0.09771286", + // "volume": "1.45340410", + // "sequence": 3853 + // } + object timestamp = this.safeInteger(ohlcv, "start"); + object open = this.safeNumber(ohlcv, "open"); + object high = this.safeNumber(ohlcv, "high"); + object low = this.safeNumber(ohlcv, "low"); + object close = this.safeNumber(ohlcv, "close"); + object volume = this.safeNumber(ohlcv, "volume"); + return new List() {timestamp, open, high, low, close, volume}; + } + + public async override Task fetchTrades(object symbol, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchTrades + * @description get the list of most recent trades for a particular symbol + * @see https://api-docs-v3.geniusyield.io/#get-trades + * @param {string} symbol unified symbol of the market to fetch trades for + * @param {int} [since] timestamp in ms of the earliest trade to fetch + * @param {int} [limit] the maximum amount of trades to fetch + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + object market = this.market(symbol); + object request = new Dictionary() { + { "market", getValue(market, "id") }, + }; + if (isTrue(!isEqual(since, null))) + { + ((IDictionary)request)["start"] = since; + } + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = mathMin(limit, 1000); + } + // [ + // { + // "fillId": "b5467d00-b13e-3fa9-8216-dd66735550fc", + // "price": "0.09771286", + // "quantity": "1.45340410", + // "quoteQuantity": "0.14201627", + // "time": 1598345638994, + // "makerSide": "buy", + // "sequence": 3853 + // }, ... + // ] + object response = await this.publicGetTrades(this.extend(request, parameters)); + return this.parseTrades(response, market, since, limit); + } + + public override object parseTrade(object trade, object market = null) + { + // + // public trades + // { + // "fillId":"a4883704-850b-3c4b-8588-020b5e4c62f1", + // "price":"0.20377008", + // "quantity":"47.58448728", + // "quoteQuantity":"9.69629509", + // "time":1642091300873, + // "makerSide":"buy", + // "type":"hybrid", // one of either: "orderBook", "hybrid", or "pool" + // "sequence":31876 + // } + // + // private trades + // { + // "fillId":"83429066-9334-3582-b710-78858b2f0d6b", + // "price":"0.20717368", + // "quantity":"15.00000000", + // "quoteQuantity":"3.10760523", + // "orderBookQuantity":"0.00000003", + // "orderBookQuoteQuantity":"0.00000001", + // "poolQuantity":"14.99999997", + // "poolQuoteQuantity":"3.10760522", + // "time":1642083351215, + // "makerSide":"sell", + // "sequence":31795, + // "market":"Genius Yield-USDC", + // "orderId":"4fe993f0-747b-11ec-bd08-79d4a0b6e47c", + // "side":"buy", + // "fee":"0.03749989", + // "feeAsset":"Genius Yield", + // "gas":"0.40507261", + // "liquidity":"taker", + // "type":"hybrid", + // "txId":"0x69f6d82a762d12e3201efd0b3e9cc1969351e3c6ea3cf07c47c66bf24a459815", + // "txStatus":"mined" + // } + // + object id = this.safeString(trade, "fillId"); + object priceString = this.safeString(trade, "price"); + object amountString = this.safeString(trade, "quantity"); + object costString = this.safeString(trade, "quoteQuantity"); + object timestamp = this.safeInteger(trade, "time"); + object marketId = this.safeString(trade, "market"); + object symbol = this.safeSymbol(marketId, market, "-"); + // this code handles the duality of public vs private trades + object makerSide = this.safeString(trade, "makerSide"); + object oppositeSide = ((bool) isTrue((isEqual(makerSide, "buy")))) ? "sell" : "buy"; + object side = this.safeString(trade, "side", oppositeSide); + object takerOrMaker = this.safeString(trade, "liquidity", "taker"); + object feeCostString = this.safeString(trade, "fee"); + object fee = null; + if (isTrue(!isEqual(feeCostString, null))) + { + object feeCurrencyId = this.safeString(trade, "feeAsset"); + fee = new Dictionary() { + { "cost", feeCostString }, + { "currency", this.safeCurrencyCode(feeCurrencyId) }, + }; + } + object orderId = this.safeString(trade, "orderId"); + return this.safeTrade(new Dictionary() { + { "info", trade }, + { "timestamp", timestamp }, + { "datetime", this.iso8601(timestamp) }, + { "symbol", symbol }, + { "id", id }, + { "order", orderId }, + { "type", "limit" }, + { "side", side }, + { "takerOrMaker", takerOrMaker }, + { "price", priceString }, + { "amount", amountString }, + { "cost", costString }, + { "fee", fee }, + }, market); + } + + public async override Task fetchTradingFees(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchTradingFees + * @description fetch the trading fees for multiple markets + * @see https://api-docs-v3.geniusyield.io/#get-api-account + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object nonce = this.uuidv1(); + object request = new Dictionary() { + { "nonce", nonce }, + }; + object response = null; + response = await this.privateGetUser(this.extend(request, parameters)); + // + // { + // "depositEnabled": true, + // "orderEnabled": true, + // "cancelEnabled": true, + // "withdrawEnabled": true, + // "totalPortfolioValueUsd": "0.00", + // "makerFeeRate": "0.0000", + // "takerFeeRate": "0.0025", + // "takerIdexFeeRate": "0.0005", + // "takerLiquidityProviderFeeRate": "0.0020" + // } + // + object maker = this.safeNumber(response, "makerFeeRate"); + object taker = this.safeNumber(response, "takerFeeRate"); + object result = new Dictionary() {}; + for (object i = 0; isLessThan(i, getArrayLength(this.symbols)); postFixIncrement(ref i)) + { + object symbol = getValue(this.symbols, i); + ((IDictionary)result)[(string)symbol] = new Dictionary() { + { "info", response }, + { "symbol", symbol }, + { "maker", maker }, + { "taker", taker }, + { "percentage", true }, + { "tierBased", false }, + }; + } + return result; + } + + public async override Task fetchOrderBook(object symbol, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchOrderBook + * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data + * @see https://api-docs-v3.geniusyield.io/#get-order-books + * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + object market = this.market(symbol); + object request = new Dictionary() { + { "market", getValue(market, "id") }, + { "level", 2 }, + }; + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = limit; + } + // { + // "sequence": 36416753, + // "bids": [ + // [ '0.09672815', "8.22284267", 1 ], + // [ '0.09672814', "1.83685554", 1 ], + // [ '0.09672143', "4.10962617", 1 ], + // [ '0.09658884', "4.03863759", 1 ], + // [ '0.09653781', "3.35730684", 1 ], + // [ '0.09624660', "2.54163586", 1 ], + // [ '0.09617490', "1.93065030", 1 ] + // ], + // "asks": [ + // [ '0.09910476', "3.22840154", 1 ], + // [ '0.09940587', "3.39796593", 1 ], + // [ '0.09948189', "4.25088898", 1 ], + // [ '0.09958362', "2.42195784", 1 ], + // [ '0.09974393', "4.25234367", 1 ], + // [ '0.09995250', "3.40192141", 1 ] + // ] + // } + object response = await this.publicGetOrderbook(this.extend(request, parameters)); + object nonce = this.safeInteger(response, "sequence"); + return new Dictionary() { + { "symbol", symbol }, + { "timestamp", null }, + { "datetime", null }, + { "nonce", nonce }, + { "bids", this.parseSide(response, "bids") }, + { "asks", this.parseSide(response, "asks") }, + }; + } + + public virtual object parseSide(object book, object side) + { + object bookSide = this.safeValue(book, side, new List() {}); + object result = new List() {}; + for (object i = 0; isLessThan(i, getArrayLength(bookSide)); postFixIncrement(ref i)) + { + object order = getValue(bookSide, i); + object price = this.safeNumber(order, 0); + object amount = this.safeNumber(order, 1); + object orderCount = this.safeInteger(order, 2); + ((IList)result).Add(new List() {price, amount, orderCount}); + } + object descending = isEqual(side, "bids"); + return this.sortBy(result, 0, descending); + } + + public async override Task fetchCurrencies(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchCurrencies + * @description fetches all available currencies on an exchange + * @see https://api-docs-v3.geniusyield.io/#get-assets + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an associative dictionary of currencies + */ + parameters ??= new Dictionary(); + object response = await this.publicGetAssets(parameters); + // + // [ + // { + // "name": "Ethereum", + // "symbol": "ETH", + // "contractAddress": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + // "assetDecimals": "18", + // "exchangeDecimals": "8", + // "maticPrice": "3029.38503483" + // }, + // ] + // + object result = new Dictionary() {}; + for (object i = 0; isLessThan(i, getArrayLength(response)); postFixIncrement(ref i)) + { + object entry = getValue(response, i); + object name = this.safeString(entry, "name"); + object currencyId = this.safeString(entry, "symbol"); + object code = this.safeCurrencyCode(currencyId); + object precision = this.parseNumber(this.parsePrecision(this.safeString(entry, "exchangeDecimals"))); + ((IDictionary)result)[(string)code] = new Dictionary() { + { "id", currencyId }, + { "code", code }, + { "info", entry }, + { "type", null }, + { "name", name }, + { "active", null }, + { "deposit", null }, + { "withdraw", null }, + { "fee", null }, + { "precision", precision }, + { "limits", new Dictionary() { + { "amount", new Dictionary() { + { "min", precision }, + { "max", null }, + } }, + { "withdraw", new Dictionary() { + { "min", precision }, + { "max", null }, + } }, + } }, + }; + } + return result; + } + + public override object parseBalance(object response) + { + object result = new Dictionary() { + { "info", response }, + { "timestamp", null }, + { "datetime", null }, + }; + for (object i = 0; isLessThan(i, getArrayLength(response)); postFixIncrement(ref i)) + { + object entry = getValue(response, i); + object currencyId = this.safeString(entry, "asset"); + object code = this.safeCurrencyCode(currencyId); + object account = this.account(); + ((IDictionary)account)["total"] = this.safeString(entry, "quantity"); + ((IDictionary)account)["free"] = this.safeString(entry, "availableForTrade"); + ((IDictionary)account)["used"] = this.safeString(entry, "locked"); + ((IDictionary)result)[(string)code] = account; + } + return this.safeBalance(result); + } + + public async override Task fetchBalance(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchBalance + * @description query for balance and get the amount of funds available for trading or funds locked in orders + * @see https://api-docs-v3.geniusyield.io/#get-balances + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure} + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object nonce1 = this.uuidv1(); + object request = new Dictionary() { + { "nonce", nonce1 }, + { "wallet", this.walletAddress }, + }; + // [ + // { + // "asset": "DIL", + // "quantity": "0.00000000", + // "availableForTrade": "0.00000000", + // "locked": "0.00000000", + // "usdValue": null + // }, ... + // ] + object extendedRequest = this.extend(request, parameters); + if (isTrue(isEqual(getValue(extendedRequest, "wallet"), null))) + { + throw new BadRequest ((string)add(this.id, " fetchBalance() wallet is undefined, set this.walletAddress or \"address\" in params")) ; + } + object response = null; + try + { + response = await this.privateGetBalances(extendedRequest); + } catch(Exception e) + { + if (isTrue(e is InvalidAddress)) + { + object walletAddress = getValue(extendedRequest, "wallet"); + await this.associateWallet(walletAddress); + response = await this.privateGetBalances(extendedRequest); + } else + { + throw e; + } + } + return this.parseBalance(response); + } + + public async override Task fetchMyTrades(object symbol = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchMyTrades + * @description fetch all trades made by the user + * @see https://api-docs-v3.geniusyield.io/#get-fills + * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint + * @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object market = null; + object request = new Dictionary() { + { "nonce", this.uuidv1() }, + { "wallet", this.walletAddress }, + }; + if (isTrue(!isEqual(symbol, null))) + { + market = this.market(symbol); + ((IDictionary)request)["market"] = getValue(market, "id"); + } + if (isTrue(!isEqual(since, null))) + { + ((IDictionary)request)["start"] = since; + } + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = limit; + } + // [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478762, + // "makerSide": "sell", + // "sequence": 5053, + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "side": "buy", + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + object extendedRequest = this.extend(request, parameters); + if (isTrue(isEqual(getValue(extendedRequest, "wallet"), null))) + { + throw new BadRequest ((string)add(this.id, " fetchMyTrades() walletAddress is undefined, set this.walletAddress or \"address\" in params")) ; + } + object response = null; + try + { + response = await this.privateGetFills(extendedRequest); + } catch(Exception e) + { + if (isTrue(e is InvalidAddress)) + { + object walletAddress = getValue(extendedRequest, "wallet"); + await this.associateWallet(walletAddress); + response = await this.privateGetFills(extendedRequest); + } else + { + throw e; + } + } + return this.parseTrades(response, market, since, limit); + } + + public async override Task fetchOrder(object id, object symbol = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchOrder + * @description fetches information on an order made by the user + * @see https://api-docs-v3.geniusyield.io/#get-orders + * @param {string} symbol unified symbol of the market the order was made in + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + object request = new Dictionary() { + { "orderId", id }, + }; + return await this.fetchOrdersHelper(symbol, null, null, this.extend(request, parameters)); + } + + public async override Task fetchOpenOrders(object symbol = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchOpenOrders + * @description fetch all unfilled currently open orders + * @see https://api-docs-v3.geniusyield.io/#get-orders + * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint + * @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + object request = new Dictionary() { + { "closed", false }, + }; + return await this.fetchOrdersHelper(symbol, since, limit, this.extend(request, parameters)); + } + + public async override Task fetchClosedOrders(object symbol = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchClosedOrders + * @description fetches information on multiple closed orders made by the user + * @see https://api-docs-v3.geniusyield.io/#get-orders + * @param {string} symbol unified market symbol of the market orders were made in + * @param {int} [since] the earliest time in ms to fetch orders for + * @param {int} [limit] the maximum number of order structures to retrieve + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + object request = new Dictionary() { + { "closed", true }, + }; + return await this.fetchOrdersHelper(symbol, since, limit, this.extend(request, parameters)); + } + + public async virtual Task fetchOrdersHelper(object symbol = null, object since = null, object limit = null, object parameters = null) + { + parameters ??= new Dictionary(); + await this.loadMarkets(); + object request = new Dictionary() { + { "nonce", this.uuidv1() }, + { "wallet", this.walletAddress }, + }; + object market = null; + if (isTrue(!isEqual(symbol, null))) + { + market = this.market(symbol); + ((IDictionary)request)["market"] = getValue(market, "id"); + } + if (isTrue(!isEqual(since, null))) + { + ((IDictionary)request)["start"] = since; + } + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = limit; + } + object response = await this.privateGetOrders(this.extend(request, parameters)); + // fetchClosedOrders / fetchOpenOrders + // [ + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // ] + // fetchOrder + // { market: "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": + // [ { fillId: "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" } ] } + if (isTrue(((response is IList) || (response.GetType().IsGenericType && response.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>)))))) + { + return ((object)this.parseOrders(response, market, since, limit)); + } else + { + return this.parseOrder(response, market); + } + } + + public virtual object parseOrderStatus(object status) + { + // https://docs.geniusyield.io/#order-states-amp-lifecycle + object statuses = new Dictionary() { + { "active", "open" }, + { "partiallyFilled", "open" }, + { "rejected", "canceled" }, + { "filled", "closed" }, + }; + return this.safeString(statuses, status, status); + } + + public override object parseOrder(object order, object market = null) + { + // + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // + object timestamp = this.safeInteger(order, "time"); + object fills = this.safeValue(order, "fills", new List() {}); + object id = this.safeString(order, "orderId"); + object clientOrderId = this.safeString(order, "clientOrderId"); + object marketId = this.safeString(order, "market"); + object side = this.safeString(order, "side"); + object symbol = this.safeSymbol(marketId, market, "-"); + object type = this.safeString(order, "type"); + object amount = this.safeString(order, "originalQuantity"); + object filled = this.safeString(order, "executedQuantity"); + object average = this.safeString(order, "avgExecutionPrice"); + object price = this.safeString(order, "price"); + object rawStatus = this.safeString(order, "status"); + object timeInForce = this.safeStringUpper(order, "timeInForce"); + object status = this.parseOrderStatus(rawStatus); + return this.safeOrder(new Dictionary() { + { "info", order }, + { "id", id }, + { "clientOrderId", clientOrderId }, + { "timestamp", timestamp }, + { "datetime", this.iso8601(timestamp) }, + { "lastTradeTimestamp", null }, + { "symbol", symbol }, + { "type", type }, + { "timeInForce", timeInForce }, + { "postOnly", null }, + { "side", side }, + { "price", price }, + { "stopPrice", null }, + { "triggerPrice", null }, + { "amount", amount }, + { "cost", null }, + { "average", average }, + { "filled", filled }, + { "remaining", null }, + { "status", status }, + { "fee", null }, + { "trades", fills }, + }, market); + } + + public async virtual Task associateWallet(object walletAddress, object parameters = null) + { + parameters ??= new Dictionary(); + object nonce = this.uuidv1(); + object noPrefix = this.remove0xPrefix(walletAddress); + object byteArray = new List {this.base16ToBinary(nonce), this.base16ToBinary(noPrefix)}; + object binary = this.binaryConcatArray(byteArray); + object hash = this.hash(binary, keccak, "hex"); + object signature = this.signMessageString(hash, this.privateKey); + // { + // "address": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "totalPortfolioValueUsd": "0.00", + // "time": 1598468353626 + // } + object request = new Dictionary() { + { "parameters", new Dictionary() { + { "nonce", nonce }, + { "wallet", walletAddress }, + } }, + { "signature", signature }, + }; + object result = await this.privatePostWallets(request); + return result; + } + + public async override Task createOrder(object symbol, object type, object side, object amount, object price = null, object parameters = null) + { + /** + * @method + * @name geniusyield#createOrder + * @description create a trade order, https://docs.geniusyield.io/#create-order + * @see https://api-docs-v3.geniusyield.io/#create-order + * @param {string} symbol unified symbol of the market to create an order in + * @param {string} type 'market' or 'limit' + * @param {string} 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 fulfilled, in units of the quote currency, ignored in market orders + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @param {bool} [params.test] set to true to test an order, no order will be created but the request will be validated + * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object testOrder = this.safeBool(parameters, "test", false); + parameters = this.omit(parameters, "test"); + object market = this.market(symbol); + object nonce = this.uuidv1(); + object typeEnum = null; + object stopLossTypeEnums = new Dictionary() { + { "stopLoss", 3 }, + { "stopLossLimit", 4 }, + { "takeProfit", 5 }, + { "takeProfitLimit", 6 }, + }; + object stopPriceString = null; + if (isTrue(isTrue(isTrue((isEqual(type, "stopLossLimit"))) || isTrue((isEqual(type, "takeProfitLimit")))) || isTrue((inOp(parameters, "stopPrice"))))) + { + if (!isTrue((inOp(parameters, "stopPrice")))) + { + throw new BadRequest ((string)add(add(add(this.id, " createOrder() stopPrice is a required parameter for "), type), "orders")) ; + } + stopPriceString = this.priceToPrecision(symbol, getValue(parameters, "stopPrice")); + } + object limitTypeEnums = new Dictionary() { + { "limit", 1 }, + { "limitMaker", 2 }, + }; + object priceString = null; + object typeLower = ((string)type).ToLower(); + object limitOrder = isGreaterThanOrEqual(getIndexOf(typeLower, "limit"), 0); + if (isTrue(inOp(limitTypeEnums, type))) + { + typeEnum = getValue(limitTypeEnums, type); + priceString = this.priceToPrecision(symbol, price); + } else if (isTrue(inOp(stopLossTypeEnums, type))) + { + typeEnum = getValue(stopLossTypeEnums, type); + priceString = this.priceToPrecision(symbol, price); + } else if (isTrue(isEqual(type, "market"))) + { + typeEnum = 0; + } else + { + throw new BadRequest ((string)add(add(add(this.id, " "), type), " is not a valid order type")) ; + } + object amountEnum = 0; // base quantity + if (isTrue(inOp(parameters, "quoteOrderQuantity"))) + { + if (isTrue(!isEqual(type, "market"))) + { + throw new NotSupported ((string)add(add(add(this.id, " createOrder() quoteOrderQuantity is not supported for "), type), " orders, only supported for market orders")) ; + } + amountEnum = 1; + amount = this.safeNumber(parameters, "quoteOrderQuantity"); + } + object sideEnum = ((bool) isTrue((isEqual(side, "buy")))) ? 0 : 1; + object walletBytes = this.remove0xPrefix(this.walletAddress); + object network = this.safeString(this.options, "network", "ETH"); + object orderVersion = this.getSupportedMapping(network, new Dictionary() { + { "ETH", 1 }, + { "BSC", 2 }, + { "MATIC", 4 }, + }); + object amountString = this.amountToPrecision(symbol, amount); + // https://docs.geniusyield.io/#time-in-force + object timeInForceEnums = new Dictionary() { + { "gtc", 0 }, + { "ioc", 2 }, + { "fok", 3 }, + }; + object defaultTimeInForce = this.safeString(this.options, "defaultTimeInForce", "gtc"); + object timeInForce = this.safeString(parameters, "timeInForce", defaultTimeInForce); + object timeInForceEnum = null; + if (isTrue(inOp(timeInForceEnums, timeInForce))) + { + timeInForceEnum = getValue(timeInForceEnums, timeInForce); + } else + { + object allOptions = new List(((IDictionary)timeInForceEnums).Keys); + object asString = String.Join(", ", ((IList)allOptions).ToArray()); + throw new BadRequest ((string)add(add(add(add(this.id, " "), timeInForce), " is not a valid timeInForce, please choose one of "), asString)) ; + } + // https://docs.geniusyield.io/#self-trade-prevention + object selfTradePreventionEnums = new Dictionary() { + { "dc", 0 }, + { "co", 1 }, + { "cn", 2 }, + { "cb", 3 }, + }; + object defaultSelfTradePrevention = this.safeString(this.options, "defaultSelfTradePrevention", "cn"); + object selfTradePrevention = this.safeString(parameters, "selfTradePrevention", defaultSelfTradePrevention); + object selfTradePreventionEnum = null; + if (isTrue(inOp(selfTradePreventionEnums, selfTradePrevention))) + { + selfTradePreventionEnum = getValue(selfTradePreventionEnums, selfTradePrevention); + } else + { + object allOptions = new List(((IDictionary)selfTradePreventionEnums).Keys); + object asString = String.Join(", ", ((IList)allOptions).ToArray()); + throw new BadRequest ((string)add(add(add(add(this.id, " "), selfTradePrevention), " is not a valid selfTradePrevention, please choose one of "), asString)) ; + } + object byteArray = new List {this.numberToBE(orderVersion, 1), this.base16ToBinary(nonce), this.base16ToBinary(walletBytes), this.encode(getValue(market, "id")), this.numberToBE(typeEnum, 1), this.numberToBE(sideEnum, 1), this.encode(amountString), this.numberToBE(amountEnum, 1)}; + if (isTrue(limitOrder)) + { + object encodedPrice = this.encode(priceString); + ((IList)byteArray).Add(encodedPrice); + } + if (isTrue(inOp(stopLossTypeEnums, type))) + { + object encodedPrice = this.encode(isTrue(stopPriceString) || isTrue(priceString)); + ((IList)byteArray).Add(encodedPrice); + } + object clientOrderId = this.safeString(parameters, "clientOrderId"); + if (isTrue(!isEqual(clientOrderId, null))) + { + ((IList)byteArray).Add(this.encode(clientOrderId)); + } + object after = new List {this.numberToBE(timeInForceEnum, 1), this.numberToBE(selfTradePreventionEnum, 1), this.numberToBE(0, 8)}; + object allBytes = this.arrayConcat(byteArray, after); + object binary = this.binaryConcatArray(allBytes); + object hash = this.hash(binary, keccak, "hex"); + object signature = this.signMessageString(hash, this.privateKey); + object request = new Dictionary() { + { "parameters", new Dictionary() { + { "nonce", nonce }, + { "market", getValue(market, "id") }, + { "side", side }, + { "type", type }, + { "wallet", this.walletAddress }, + { "selfTradePrevention", selfTradePrevention }, + } }, + { "signature", signature }, + }; + if (isTrue(!isEqual(type, "market"))) + { + ((IDictionary)getValue(request, "parameters"))["timeInForce"] = timeInForce; + } + if (isTrue(limitOrder)) + { + ((IDictionary)getValue(request, "parameters"))["price"] = priceString; + } + if (isTrue(inOp(stopLossTypeEnums, type))) + { + ((IDictionary)getValue(request, "parameters"))["stopPrice"] = isTrue(stopPriceString) || isTrue(priceString); + } + if (isTrue(isEqual(amountEnum, 0))) + { + ((IDictionary)getValue(request, "parameters"))["quantity"] = amountString; + } else + { + ((IDictionary)getValue(request, "parameters"))["quoteOrderQuantity"] = amountString; + } + if (isTrue(!isEqual(clientOrderId, null))) + { + ((IDictionary)getValue(request, "parameters"))["clientOrderId"] = clientOrderId; + } + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txStatus": "pending" + // } + // ], + // "avgExecutionPrice": "0.09905990" + // } + // we don't use extend here because it is a signed endpoint + object response = null; + if (isTrue(testOrder)) + { + response = await this.privatePostOrdersTest(request); + } else + { + response = await this.privatePostOrders(request); + } + return this.parseOrder(response, market); + } + + public async override Task withdraw(object code, object amount, object address, object tag = null, object parameters = null) + { + /** + * @method + * @name geniusyield#withdraw + * @description make a withdrawal + * @see https://api-docs-v3.geniusyield.io/#withdraw-funds + * @param {string} code unified currency code + * @param {float} amount the amount to withdraw + * @param {string} address the address to withdraw to + * @param {string} tag + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure} + */ + parameters ??= new Dictionary(); + var tagparametersVariable = this.handleWithdrawTagAndParams(tag, parameters); + tag = ((IList)tagparametersVariable)[0]; + parameters = ((IList)tagparametersVariable)[1]; + this.checkRequiredCredentials(); + await this.loadMarkets(); + object nonce = this.uuidv1(); + object amountString = this.currencyToPrecision(code, amount); + object currency = this.currency(code); + object walletBytes = this.remove0xPrefix(this.walletAddress); + object byteArray = new List {this.base16ToBinary(nonce), this.base16ToBinary(walletBytes), this.encode(getValue(currency, "id")), this.encode(amountString), this.numberToBE(1, 1)}; + object binary = this.binaryConcatArray(byteArray); + object hash = this.hash(binary, keccak, "hex"); + object signature = this.signMessageString(hash, this.privateKey); + object request = new Dictionary() { + { "parameters", new Dictionary() { + { "nonce", nonce }, + { "wallet", address }, + { "asset", getValue(currency, "id") }, + { "quantity", amountString }, + } }, + { "signature", signature }, + }; + object response = await this.privatePostWithdrawals(request); + // + // { + // "withdrawalId": "a61dcff0-ec4d-11ea-8b83-c78a6ecb3180", + // "asset": "ETH", + // "assetContractAddress": "0x0000000000000000000000000000000000000000", + // "quantity": "0.20000000", + // "time": 1598962883190, + // "fee": "0.00024000", + // "txStatus": "pending", + // "txId": null + // } + // + return this.parseTransaction(response, currency); + } + + public async override Task cancelAllOrders(object symbol = null, object parameters = null) + { + /** + * @method + * @name geniusyield#cancelAllOrders + * @description cancel all open orders + * @see https://api-docs-v3.geniusyield.io/#cancel-order + * @param {string} symbol unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object market = null; + if (isTrue(!isEqual(symbol, null))) + { + market = this.market(symbol); + } + object nonce = this.uuidv1(); + object request = new Dictionary() { + { "parameters", new Dictionary() { + { "nonce", nonce }, + { "wallet", this.walletAddress }, + } }, + }; + object walletBytes = this.remove0xPrefix(this.walletAddress); + object byteArray = new List {this.base16ToBinary(nonce), this.base16ToBinary(walletBytes)}; + if (isTrue(!isEqual(market, null))) + { + ((IList)byteArray).Add(this.encode(getValue(market, "id"))); + ((IDictionary)getValue(request, "parameters"))["market"] = getValue(market, "id"); + } + object binary = this.binaryConcatArray(byteArray); + object hash = this.hash(binary, keccak, "hex"); + object signature = this.signMessageString(hash, this.privateKey); + ((IDictionary)request)["signature"] = signature; + // [ { orderId: "688336f0-ec50-11ea-9842-b332f8a34d0e" } ] + object response = await this.privateDeleteOrders(this.extend(request, parameters)); + return this.parseOrders(response, market); + } + + public async override Task cancelOrder(object id, object symbol = null, object parameters = null) + { + /** + * @method + * @name geniusyield#cancelOrder + * @description cancels an open order + * @see https://api-docs-v3.geniusyield.io/#cancel-order + * @param {string} id order id + * @param {string} symbol unified symbol of the market the order was made in + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} + */ + parameters ??= new Dictionary(); + this.checkRequiredCredentials(); + await this.loadMarkets(); + object market = null; + if (isTrue(!isEqual(symbol, null))) + { + market = this.market(symbol); + } + object nonce = this.uuidv1(); + object walletBytes = this.remove0xPrefix(this.walletAddress); + object byteArray = new List {this.base16ToBinary(nonce), this.base16ToBinary(walletBytes), this.encode(id)}; + object binary = this.binaryConcatArray(byteArray); + object hash = this.hash(binary, keccak, "hex"); + object signature = this.signMessageString(hash, this.privateKey); + object request = new Dictionary() { + { "parameters", new Dictionary() { + { "nonce", nonce }, + { "wallet", this.walletAddress }, + { "orderId", id }, + } }, + { "signature", signature }, + }; + // [ { orderId: "688336f0-ec50-11ea-9842-b332f8a34d0e" } ] + object response = await this.privateDeleteOrders(this.extend(request, parameters)); + object canceledOrder = this.safeDict(response, 0); + return this.parseOrder(canceledOrder, market); + } + + public override object handleErrors(object code, object reason, object url, object method, object headers, object body, object response, object requestHeaders, object requestBody) + { + object errorCode = this.safeString(response, "code"); + object message = this.safeString(response, "message"); + if (isTrue(!isEqual(errorCode, null))) + { + this.throwExactlyMatchedException(getValue(this.exceptions, "exact"), errorCode, message); + throw new ExchangeError ((string)add(add(this.id, " "), message)) ; + } + return null; + } + + public async virtual Task fetchDeposit(object id, object code = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchDeposit + * @description fetch information on a deposit + * @see https://api-docs-v3.geniusyield.io/#get-deposits + * @param {string} id deposit id + * @param {string} code not used by geniusyield fetchDeposit () + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure} + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + object nonce = this.uuidv1(); + object request = new Dictionary() { + { "nonce", nonce }, + { "wallet", this.walletAddress }, + { "depositId", id }, + }; + object response = await this.privateGetDeposits(this.extend(request, parameters)); + return this.parseTransaction(response); + } + + public async override Task fetchDeposits(object code = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchDeposits + * @description fetch all deposits made to an account + * @see https://api-docs-v3.geniusyield.io/#get-deposits + * @param {string} code unified currency code + * @param {int} [since] the earliest time in ms to fetch deposits for + * @param {int} [limit] the maximum number of deposits structures to retrieve + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure} + */ + parameters ??= new Dictionary(); + parameters = this.extend(new Dictionary() { + { "method", "privateGetDeposits" }, + }, parameters); + return await this.fetchTransactionsHelper(code, since, limit, parameters); + } + + public async override Task fetchStatus(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchStatus + * @description the latest known information on the availability of the exchange API + * @see https://api-docs-v3.geniusyield.io/#get-ping + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [status structure]{@link https://docs.ccxt.com/#/?id=exchange-status-structure} + */ + parameters ??= new Dictionary(); + object response = await this.publicGetPing(parameters); + return new Dictionary() { + { "status", "ok" }, + { "updated", null }, + { "eta", null }, + { "url", null }, + { "info", response }, + }; + } + + public async override Task fetchTime(object parameters = null) + { + /** + * @method + * @name geniusyield#fetchTime + * @description fetches the current integer timestamp in milliseconds from the exchange server + * @see https://api-docs-v3.geniusyield.io/#get-time + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {int} the current integer timestamp in milliseconds from the exchange server + */ + parameters ??= new Dictionary(); + object response = await this.publicGetTime(parameters); + // + // { serverTime: "1655258263236" } + // + return this.safeInteger(response, "serverTime"); + } + + public async virtual Task fetchWithdrawal(object id, object code = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchWithdrawal + * @description fetch data on a currency withdrawal via the withdrawal id + * @see https://api-docs-v3.geniusyield.io/#get-withdrawals + * @param {string} id withdrawal id + * @param {string} code not used by geniusyield.fetchWithdrawal + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure} + */ + parameters ??= new Dictionary(); + await this.loadMarkets(); + object nonce = this.uuidv1(); + object request = new Dictionary() { + { "nonce", nonce }, + { "wallet", this.walletAddress }, + { "withdrawalId", id }, + }; + object response = await this.privateGetWithdrawals(this.extend(request, parameters)); + return this.parseTransaction(response); + } + + public async override Task fetchWithdrawals(object code = null, object since = null, object limit = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchWithdrawals + * @description fetch all withdrawals made from an account + * @see https://api-docs-v3.geniusyield.io/#get-withdrawals + * @param {string} code unified currency code + * @param {int} [since] the earliest time in ms to fetch withdrawals for + * @param {int} [limit] the maximum number of withdrawals structures to retrieve + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure} + */ + parameters ??= new Dictionary(); + parameters = this.extend(new Dictionary() { + { "method", "privateGetWithdrawals" }, + }, parameters); + return await this.fetchTransactionsHelper(code, since, limit, parameters); + } + + public async virtual Task fetchTransactionsHelper(object code = null, object since = null, object limit = null, object parameters = null) + { + parameters ??= new Dictionary(); + await this.loadMarkets(); + object nonce = this.uuidv1(); + object request = new Dictionary() { + { "nonce", nonce }, + { "wallet", this.walletAddress }, + }; + object currency = null; + if (isTrue(!isEqual(code, null))) + { + currency = this.currency(code); + ((IDictionary)request)["asset"] = getValue(currency, "id"); + } + if (isTrue(!isEqual(since, null))) + { + ((IDictionary)request)["start"] = since; + } + if (isTrue(!isEqual(limit, null))) + { + ((IDictionary)request)["limit"] = limit; + } + // [ + // { + // "depositId": "e9970cc0-eb6b-11ea-9e89-09a5ebc1f98e", + // "asset": "ETH", + // "quantity": "1.00000000", + // "txId": "0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142", + // "txTime": 1598865853000, + // "confirmationTime": 1598865930231 + // } + // ] + object method = getValue(parameters, "method"); + parameters = this.omit(parameters, "method"); + object response = null; + if (isTrue(isEqual(method, "privateGetDeposits"))) + { + response = await this.privateGetDeposits(this.extend(request, parameters)); + } else if (isTrue(isEqual(method, "privateGetWithdrawals"))) + { + response = await this.privateGetWithdrawals(this.extend(request, parameters)); + } else + { + throw new NotSupported ((string)add(this.id, " fetchTransactionsHelper() not support this method")) ; + } + return this.parseTransactions(response, currency, since, limit); + } + + public virtual object parseTransactionStatus(object status) + { + object statuses = new Dictionary() { + { "mined", "ok" }, + }; + return this.safeString(statuses, status, status); + } + + public override object parseTransaction(object transaction, object currency = null) + { + // + // fetchDeposits + // + // { + // "depositId": "e9970cc0-eb6b-11ea-9e89-09a5ebc1f98f", + // "asset": "ETH", + // "quantity": "1.00000000", + // "txId": "0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142", + // "txTime": 1598865853000, + // "confirmationTime": 1598865930231 + // } + // + // fetchWithdrwalas + // + // { + // "withdrawalId": "a62d8760-ec4d-11ea-9fa6-47904c19499b", + // "asset": "ETH", + // "assetContractAddress": "0x0000000000000000000000000000000000000000", + // "quantity": "0.20000000", + // "time": 1598962883288, + // "fee": "0.00024000", + // "txId": "0x305e9cdbaa85ad029f50578d13d31d777c085de573ed5334d95c19116d8c03ce", + // "txStatus": "mined" + // } + // + // withdraw + // + // { + // "withdrawalId": "a61dcff0-ec4d-11ea-8b83-c78a6ecb3180", + // "asset": "ETH", + // "assetContractAddress": "0x0000000000000000000000000000000000000000", + // "quantity": "0.20000000", + // "time": 1598962883190, + // "fee": "0.00024000", + // "txStatus": "pending", + // "txId": null + // } + // + object type = null; + if (isTrue(inOp(transaction, "depositId"))) + { + type = "deposit"; + } else if (isTrue(isTrue((inOp(transaction, "withdrawId"))) || isTrue((inOp(transaction, "withdrawalId"))))) + { + type = "withdrawal"; + } + object id = this.safeString2(transaction, "depositId", "withdrawId"); + id = this.safeString(transaction, "withdrawalId", id); + object code = this.safeCurrencyCode(this.safeString(transaction, "asset"), currency); + object amount = this.safeNumber(transaction, "quantity"); + object txid = this.safeString(transaction, "txId"); + object timestamp = this.safeInteger2(transaction, "txTime", "time"); + object fee = null; + if (isTrue(inOp(transaction, "fee"))) + { + fee = new Dictionary() { + { "cost", this.safeNumber(transaction, "fee") }, + { "currency", "ETH" }, + }; + } + object rawStatus = this.safeString(transaction, "txStatus"); + object status = this.parseTransactionStatus(rawStatus); + object updated = this.safeInteger(transaction, "confirmationTime"); + return new Dictionary() { + { "info", transaction }, + { "id", id }, + { "txid", txid }, + { "timestamp", timestamp }, + { "datetime", this.iso8601(timestamp) }, + { "network", null }, + { "address", null }, + { "addressTo", null }, + { "addressFrom", null }, + { "tag", null }, + { "tagTo", null }, + { "tagFrom", null }, + { "type", type }, + { "amount", amount }, + { "currency", code }, + { "status", status }, + { "updated", updated }, + { "comment", null }, + { "internal", null }, + { "fee", fee }, + }; + } + + public override object calculateRateLimiterCost(object api, object method, object path, object parameters, object config = null) + { + config ??= new Dictionary(); + object hasApiKey = (!isEqual(this.apiKey, null)); + object hasSecret = (!isEqual(this.secret, null)); + object hasWalletAddress = (!isEqual(this.walletAddress, null)); + object hasPrivateKey = (!isEqual(this.privateKey, null)); + object defaultCost = this.safeValue(config, "cost", 1); + object authenticated = isTrue(isTrue(isTrue(hasApiKey) && isTrue(hasSecret)) && isTrue(hasWalletAddress)) && isTrue(hasPrivateKey); + return ((bool) isTrue(authenticated)) ? (divide(defaultCost, 2)) : defaultCost; + } + + public async override Task fetchDepositAddress(object code = null, object parameters = null) + { + /** + * @method + * @name geniusyield#fetchDepositAddress + * @description fetch the Polygon address of the wallet + * @see https://api-docs-v3.geniusyield.io/#get-wallets + * @param {string} code not used by geniusyield + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure} + */ + parameters ??= new Dictionary(); + object request = new Dictionary() {}; + ((IDictionary)request)["nonce"] = this.uuidv1(); + object response = await this.privateGetWallets(this.extend(request, parameters)); + // + // [ + // { + // address: "0x37A1827CA64C94A26028bDCb43FBDCB0bf6DAf5B", + // totalPortfolioValueUsd: "0.00", + // time: "1678342148086" + // }, + // { + // address: "0x0Ef3456E616552238B0c562d409507Ed6051A7b3", + // totalPortfolioValueUsd: "15.90", + // time: "1691697811659" + // } + // ] + // + return this.parseDepositAddress(response); + } + + public override object parseDepositAddress(object depositAddress, object currency = null) + { + // + // [ + // { + // address: "0x37A1827CA64C94A26028bDCb43FBDCB0bf6DAf5B", + // totalPortfolioValueUsd: "0.00", + // time: "1678342148086" + // }, + // { + // address: "0x0Ef3456E616552238B0c562d409507Ed6051A7b3", + // totalPortfolioValueUsd: "15.90", + // time: "1691697811659" + // } + // ] + // + object length = getArrayLength(depositAddress); + object entry = this.safeDict(depositAddress, subtract(length, 1)); + object address = this.safeString(entry, "address"); + this.checkAddress(address); + return new Dictionary() { + { "info", depositAddress }, + { "currency", null }, + { "address", address }, + { "tag", null }, + { "network", "MATIC" }, + }; + } + + public override object sign(object path, object api = null, object method = null, object parameters = null, object headers = null, object body = null) + { + api ??= "public"; + method ??= "GET"; + parameters ??= new Dictionary(); + object network = this.safeString(this.options, "network", "ETH"); + object version = this.safeString(this.options, "version", "v1"); + object url = add(add(add(add(getValue(getValue(this.urls, "api"), network), "/"), version), "/"), path); + object keys = new List(((IDictionary)parameters).Keys); + object length = getArrayLength(keys); + object query = null; + if (isTrue(isGreaterThan(length, 0))) + { + if (isTrue(isEqual(method, "GET"))) + { + query = this.urlencode(parameters); + url = add(add(url, "?"), query); + } else + { + body = this.json(parameters); + } + } + headers = new Dictionary() { + { "Content-Type", "application/json" }, + }; + if (isTrue(!isEqual(this.apiKey, null))) + { + ((IDictionary)headers)["Genius Yield-API-Key"] = this.apiKey; + } + if (isTrue(isEqual(api, "private"))) + { + object payload = null; + if (isTrue(isEqual(method, "GET"))) + { + payload = query; + } else + { + payload = body; + } + ((IDictionary)headers)["Genius Yield-HMAC-Signature"] = this.hmac(this.encode(payload), this.encode(this.secret), sha256, "hex"); + } + return new Dictionary() { + { "url", url }, + { "method", method }, + { "body", body }, + { "headers", headers }, + }; + } + + public override object remove0xPrefix(object hexData) + { + if (isTrue(isEqual(slice(hexData, 0, 2), "0x"))) + { + return slice(hexData, 2, null); + } else + { + return hexData; + } + } + + public virtual object hashMessage(object message) + { + // takes a hex encoded message + object binaryMessage = this.base16ToBinary(this.remove0xPrefix(message)); + object prefix = this.encode(add("Ethereum Signed Message:\n", getValue(binaryMessage, "byteLength"))); + return add("0x", this.hash(this.binaryConcat(prefix, binaryMessage), keccak, "hex")); + } + + public virtual object signHash(object hash, object privateKey) + { + object signature = ecdsa(slice(hash, -64, null), slice(privateKey, -64, null), secp256k1, null); + return new Dictionary() { + { "r", add("0x", getValue(signature, "r")) }, + { "s", add("0x", getValue(signature, "s")) }, + { "v", add(27, getValue(signature, "v")) }, + }; + } + + public virtual object signMessage(object message, object privateKey) + { + return this.signHash(this.hashMessage(message), slice(privateKey, -64, null)); + } + + public virtual object signMessageString(object message, object privateKey) + { + // still takes the input as a hex string + // same as above but returns a string instead of an object + object signature = this.signMessage(message, privateKey); + return add(add(getValue(signature, "r"), this.remove0xPrefix(getValue(signature, "s"))), this.binaryToBase16(this.numberToBE(getValue(signature, "v"), 1))); + } +} diff --git a/cs/ccxt/wrappers/geniusyield.cs b/cs/ccxt/wrappers/geniusyield.cs new file mode 100644 index 000000000000..53e2bb88cf7a --- /dev/null +++ b/cs/ccxt/wrappers/geniusyield.cs @@ -0,0 +1,601 @@ +namespace ccxt; + +// 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 + + +public partial class geniusyield +{ + /// + /// retrieves data on all markets for geniusyield + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object[] an array of objects representing market data. + public async Task> FetchMarkets(Dictionary parameters = null) + { + var res = await this.fetchMarkets(parameters); + return ((IList)res).Select(item => new MarketInterface(item)).ToList(); + } + /// + /// fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}. + public async Task FetchTicker(string symbol, Dictionary parameters = null) + { + var res = await this.fetchTicker(symbol, parameters); + return new Ticker(res); + } + /// + /// fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}. + public async Task FetchTickers(List symbols = null, Dictionary parameters = null) + { + var res = await this.fetchTickers(symbols, parameters); + return new Tickers(res); + } + /// + /// fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : timestamp in ms of the earliest candle to fetch + /// + /// + /// + /// limit + /// + /// int : the maximum amount of candles to fetch + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// int[][] A list of candles ordered as timestamp, open, high, low, close, volume. + public async Task> FetchOHLCV(string symbol, string timeframe = "1m", Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchOHLCV(symbol, timeframe, since, limit, parameters); + return ((IList)res).Select(item => new OHLCV(item)).ToList(); + } + /// + /// get the list of most recent trades for a particular symbol + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : timestamp in ms of the earliest trade to fetch + /// + /// + /// + /// limit + /// + /// int : the maximum amount of trades to fetch + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// Trade[] a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}. + public async Task> FetchTrades(string symbol, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchTrades(symbol, since, limit, parameters); + return ((IList)res).Select(item => new Trade(item)).ToList(); + } + /// + /// fetch the trading fees for multiple markets + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols. + public async Task FetchTradingFees(Dictionary parameters = null) + { + var res = await this.fetchTradingFees(parameters); + return new TradingFees(res); + } + /// + /// fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data + /// + /// + /// See
+ /// + /// + /// limit + /// + /// int : the maximum amount of order book entries to return + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols. + public async Task FetchOrderBook(string symbol, Int64? limit2 = 0, Dictionary parameters = null) + { + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchOrderBook(symbol, limit, parameters); + return new OrderBook(res); + } + /// + /// query for balance and get the amount of funds available for trading or funds locked in orders + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}. + public async Task FetchBalance(Dictionary parameters = null) + { + var res = await this.fetchBalance(parameters); + return new Balances(res); + } + /// + /// fetch all trades made by the user + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : the earliest time in ms to fetch trades for + /// + /// + /// + /// limit + /// + /// int : the maximum number of trades structures to retrieve + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// Trade[] a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}. + public async Task> FetchMyTrades(string symbol = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchMyTrades(symbol, since, limit, parameters); + return ((IList)res).Select(item => new Trade(item)).ToList(); + } + /// + /// fetches information on an order made by the user + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task FetchOrder(string id, string symbol = null, Dictionary parameters = null) + { + var res = await this.fetchOrder(id, symbol, parameters); + return new Order(res); + } + /// + /// fetch all unfilled currently open orders + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : the earliest time in ms to fetch open orders for + /// + /// + /// + /// limit + /// + /// int : the maximum number of open orders structures to retrieve + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// Order[] a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task> FetchOpenOrders(string symbol = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchOpenOrders(symbol, since, limit, parameters); + return ((IList)res).Select(item => new Order(item)).ToList(); + } + /// + /// fetches information on multiple closed orders made by the user + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : the earliest time in ms to fetch orders for + /// + /// + /// + /// limit + /// + /// int : the maximum number of order structures to retrieve + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// Order[] a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task> FetchClosedOrders(string symbol = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchClosedOrders(symbol, since, limit, parameters); + return ((IList)res).Select(item => new Order(item)).ToList(); + } + public async Task> FetchOrdersHelper(string symbol = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchOrdersHelper(symbol, since, limit, parameters); + return ((Dictionary)res); + } + /// + /// create a trade order, https://docs.geniusyield.io/#create-order + /// + /// + /// See
+ /// + /// + /// price + /// + /// float : the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + /// params.test + /// + /// bool : set to true to test an order, no order will be created but the request will be validated + /// + /// + /// + ///
+ /// object an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task CreateOrder(string symbol, string type, string side, double amount, double? price2 = 0, Dictionary parameters = null) + { + var price = price2 == 0 ? null : (object)price2; + var res = await this.createOrder(symbol, type, side, amount, price, parameters); + return new Order(res); + } + /// + /// make a withdrawal + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}. + public async Task Withdraw(string code, double amount, string address, object tag = null, Dictionary parameters = null) + { + var res = await this.withdraw(code, amount, address, tag, parameters); + return new Transaction(res); + } + /// + /// cancel all open orders + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object[] a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task> CancelAllOrders(string symbol = null, Dictionary parameters = null) + { + var res = await this.cancelAllOrders(symbol, parameters); + return ((IList)res).Select(item => new Order(item)).ToList(); + } + /// + /// cancels an open order + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}. + public async Task CancelOrder(string id, string symbol = null, Dictionary parameters = null) + { + var res = await this.cancelOrder(id, symbol, parameters); + return new Order(res); + } + /// + /// fetch information on a deposit + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}. + public async Task FetchDeposit(string id, string code = null, Dictionary parameters = null) + { + var res = await this.fetchDeposit(id, code, parameters); + return new Transaction(res); + } + /// + /// fetch all deposits made to an account + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : the earliest time in ms to fetch deposits for + /// + /// + /// + /// limit + /// + /// int : the maximum number of deposits structures to retrieve + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object[] a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}. + public async Task> FetchDeposits(string code = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchDeposits(code, since, limit, parameters); + return ((IList)res).Select(item => new Transaction(item)).ToList(); + } + /// + /// the latest known information on the availability of the exchange API + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [status structure]{@link https://docs.ccxt.com/#/?id=exchange-status-structure}. + public async Task> FetchStatus(Dictionary parameters = null) + { + var res = await this.fetchStatus(parameters); + return ((Dictionary)res); + } + /// + /// fetches the current integer timestamp in milliseconds from the exchange server + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// int the current integer timestamp in milliseconds from the exchange server. + public async Task FetchTime(Dictionary parameters = null) + { + var res = await this.fetchTime(parameters); + return (Int64)res; + } + /// + /// fetch data on a currency withdrawal via the withdrawal id + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object a [transaction structure]{@link https://docs.ccxt.com/#/?id=transaction-structure}. + public async Task FetchWithdrawal(string id, string code = null, Dictionary parameters = null) + { + var res = await this.fetchWithdrawal(id, code, parameters); + return new Transaction(res); + } + /// + /// fetch all withdrawals made from an account + /// + /// + /// See
+ /// + /// + /// since + /// + /// int : the earliest time in ms to fetch withdrawals for + /// + /// + /// + /// limit + /// + /// int : the maximum number of withdrawals structures to retrieve + /// + /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object[] a list of [transaction structures]{@link https://docs.ccxt.com/#/?id=transaction-structure}. + public async Task> FetchWithdrawals(string code = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchWithdrawals(code, since, limit, parameters); + return ((IList)res).Select(item => new Transaction(item)).ToList(); + } + public async Task> FetchTransactionsHelper(string code = null, Int64? since2 = 0, Int64? limit2 = 0, Dictionary parameters = null) + { + var since = since2 == 0 ? null : (object)since2; + var limit = limit2 == 0 ? null : (object)limit2; + var res = await this.fetchTransactionsHelper(code, since, limit, parameters); + return ((IList)res).Select(item => new Transaction(item)).ToList(); + } + /// + /// fetch the Polygon address of the wallet + /// + /// + /// See
+ /// + /// + /// params + /// + /// object : extra parameters specific to the exchange API endpoint + /// + /// + /// + ///
+ /// object an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure}. + public async Task> FetchDepositAddress(string code = null, Dictionary parameters = null) + { + var res = await this.fetchDepositAddress(code, parameters); + return ((Dictionary)res); + } +} diff --git a/dist/cjs/src/abstract/geniusyield.js b/dist/cjs/src/abstract/geniusyield.js new file mode 100644 index 000000000000..7e4804edc24a --- /dev/null +++ b/dist/cjs/src/abstract/geniusyield.js @@ -0,0 +1,9 @@ +'use strict'; + +var Exchange$1 = require('../base/Exchange.js'); + +// ------------------------------------------------------------------------------- +class Exchange extends Exchange$1["default"] { +} + +module.exports = Exchange; diff --git a/dist/cjs/src/geniusyield.js b/dist/cjs/src/geniusyield.js new file mode 100644 index 000000000000..790edbc8a554 --- /dev/null +++ b/dist/cjs/src/geniusyield.js @@ -0,0 +1,302 @@ +'use strict'; + +var geniusyield$1 = require('./abstract/geniusyield.js'); +var number = require('./base/functions/number.js'); +var errors = require('./base/errors.js'); + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +/** + * @class geniusyield + * @augments Exchange + */ +class geniusyield extends geniusyield$1 { + safeMarket(marketId = undefined, market = undefined, delimiter = undefined, marketType = undefined) { + const isOption = (marketId !== undefined) && ((marketId.indexOf('-C') > -1) || (marketId.indexOf('-P') > -1)); + if (isOption && !(marketId in this.markets_by_id)) { + // handle expired option contracts + return this.createExpiredOptionMarket(marketId); + } + return super.safeMarket(marketId, market, delimiter, marketType); + } + describe() { + return this.deepExtend(super.describe(), { + 'id': 'geniusyield', + 'name': 'Genius Yield', + 'countries': ['CH'], + 'rateLimit': 1000, + 'version': 'v0', + 'pro': false, + 'dex': true, + 'certified': false, + 'requiresWeb3': true, + 'has': { + 'CORS': undefined, + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'addMargin': false, + 'cancelAllOrders': false, + 'cancelOrder': false, + 'cancelOrders': false, + 'closeAllPositions': false, + 'closePosition': false, + 'createDepositAddress': false, + 'createOrder': false, + 'createReduceOnlyOrder': false, + 'createStopLimitOrder': false, + 'createStopMarketOrder': false, + 'createStopOrder': false, + 'fetchBalance': true, + 'fetchBorrowRateHistories': false, + 'fetchBorrowRateHistory': false, + 'fetchClosedOrders': false, + 'fetchCrossBorrowRate': false, + 'fetchCrossBorrowRates': false, + 'fetchCurrencies': false, + 'fetchDeposit': false, + 'fetchDepositAddress': false, + 'fetchDepositAddresses': false, + 'fetchDepositAddressesByNetwork': false, + 'fetchDeposits': false, + 'fetchFundingHistory': false, + 'fetchFundingRate': false, + 'fetchFundingRateHistory': false, + 'fetchFundingRates': false, + 'fetchIndexOHLCV': false, + 'fetchIsolatedBorrowRate': false, + 'fetchIsolatedBorrowRates': false, + 'fetchLeverage': false, + 'fetchLeverageTiers': false, + 'fetchMarginMode': false, + 'fetchMarkets': true, + 'fetchMarkOHLCV': false, + 'fetchMyTrades': false, + 'fetchOHLCV': false, + 'fetchOpenInterestHistory': false, + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': false, + 'fetchOrders': false, + 'fetchPosition': false, + 'fetchPositionHistory': false, + 'fetchPositionMode': false, + 'fetchPositions': false, + 'fetchPositionsForSymbol': false, + 'fetchPositionsHistory': false, + 'fetchPositionsRisk': false, + 'fetchPremiumIndexOHLCV': false, + 'fetchStatus': false, + 'fetchTicker': false, + 'fetchTickers': false, + 'fetchTime': false, + 'fetchTrades': false, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransactions': false, + 'fetchWithdrawal': false, + 'fetchWithdrawals': false, + 'reduceMargin': false, + 'sandbox': false, + 'setLeverage': false, + 'setMarginMode': false, + 'setPositionMode': false, + 'transfer': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'api': { + 'preprod': 'http://localhost:8082', + 'mainnet': 'http://localhost:8082', + }, + 'www': 'https://www.geniusyield.co/', + 'doc': [ + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ], + }, + 'api': { + 'public': { + 'get': {}, + }, + 'private': { + 'get': { + 'balances/{address}': 10, + 'markets': 10, + 'trading-fees': 10, + 'settings': 10, + }, + }, + }, + 'headers': { + 'X-Gate-Channel-Id': 'ccxt', + }, + 'options': { + 'defaultTimeInForce': 'utc', + 'defaultSelfTradePrevention': 'cn', + 'network': 'mainnet', + }, + 'exceptions': { + 'exact': { + 'INVALID_ORDER_QUANTITY': errors.InvalidOrder, + 'INSUFFICIENT_FUNDS': errors.InsufficientFunds, + 'SERVICE_UNAVAILABLE': errors.ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': errors.DDoSProtection, + 'INVALID_PARAMETER': errors.BadRequest, + 'WALLET_NOT_ASSOCIATED': errors.InvalidAddress, + 'INVALID_WALLET_SIGNATURE': errors.AuthenticationError, + }, + }, + 'requiredCredentials': { + 'walletAddress': false, + 'privateKey': false, + 'apiKey': true, + 'secret': false, + }, + 'precisionMode': number.TICK_SIZE, + 'paddingMode': number.PAD_WITH_ZERO, + 'commonCurrencies': {}, + }); + } + async fetchMarkets(params = {}) { + this.checkRequiredCredentials(); + /** + * @method + * @name geniusyield#fetchMarkets + * @description retrieves data on all markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-markets + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} an array of objects representing market data + */ + const markets = await this.privateGetMarkets(params); + // [ + // { + // "market_id": "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + // "base_asset": "lovelace", + // "target_asset": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + // } + // ] + const fees = await this.privateGetTradingFees(); + // { + // "flat_maker_fee": "1000000", + // "flat_taker_fee": "1000000", + // "percentage_maker_fee": "0.3", + // "percentage_taker_fee": "0.3" + // } + const maker = this.safeNumber(fees, 'percentage_maker_fee'); + const taker = this.safeNumber(fees, 'percentage_taker_fee'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const entry = markets[i]; + const marketId = this.safeString(entry, 'market_id'); + const baseId = this.safeString(entry, 'base_asset'); + const quoteId = this.safeString(entry, 'target_asset'); + const base = this.safeCurrencyCode(baseId); + const quote = this.safeCurrencyCode(quoteId); + result.push({ + 'id': marketId, + 'symbol': marketId, + 'base': base, + 'quote': quote, + 'settle': undefined, + 'baseId': baseId, + 'quoteId': quoteId, + 'settleId': undefined, + 'type': 'spot', + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'active': true, + 'contract': false, + 'linear': undefined, + 'inverse': undefined, + 'taker': taker, + 'maker': maker, + 'contractSize': undefined, + 'expiry': undefined, + 'expiryDatetime': undefined, + 'strike': undefined, + 'optionType': undefined, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'leverage': { + 'min': undefined, + 'max': undefined, + }, + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'created': undefined, + 'info': entry, + }); + } + return result; + } + parseBalance(response) { + const result = { + 'info': response, + 'timestamp': undefined, + 'datetime': undefined, + }; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const currencyId = this.safeString(entry, 'asset'); + const code = this.safeCurrencyCode(currencyId); + const account = this.account(); + account['total'] = this.safeString(entry, 'quantity'); + account['free'] = this.safeString(entry, 'availableForTrade'); + account['used'] = this.safeString(entry, 'locked'); + result[code] = account; + } + return this.safeBalance(result); + } + async fetchBalance(params = {}) { + const settings = await this.privateGetSettings(params); + const address = this.safeString(settings, 'address'); + const request = { + 'address': address, + }; + const balances = await this.privateGetBalancesAddress(this.extend(request, params)); + return this.safeBalance(balances); + } + sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + this.checkRequiredCredentials(); + const network = this.safeString(this.options, 'network', 'mainnet'); + const version = this.safeString(this.options, 'version', 'v0'); + const url = this.urls['api'][network] + '/' + version + '/' + this.implodeParams(path, params); + headers = { + 'Content-Type': 'application/json', + }; + if (this.apiKey !== undefined) { + headers['api-key'] = this.apiKey; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +} + +module.exports = geniusyield; diff --git a/js/ccxt.d.ts b/js/ccxt.d.ts index c95b3a2961e7..c7f62540bea1 100644 --- a/js/ccxt.d.ts +++ b/js/ccxt.d.ts @@ -65,6 +65,7 @@ import fmfwio from './src/fmfwio.js'; import gate from './src/gate.js'; import gateio from './src/gateio.js'; import gemini from './src/gemini.js'; +import geniusyield from './src/geniusyield.js'; import hitbtc from './src/hitbtc.js'; import hitbtc3 from './src/hitbtc3.js'; import hollaex from './src/hollaex.js'; @@ -241,6 +242,7 @@ declare const exchanges: { gate: typeof gate; gateio: typeof gateio; gemini: typeof gemini; + geniusyield: typeof geniusyield; hitbtc: typeof hitbtc; hitbtc3: typeof hitbtc3; hollaex: typeof hollaex; @@ -494,6 +496,7 @@ declare const ccxt: { gate: typeof gate; gateio: typeof gateio; gemini: typeof gemini; + geniusyield: typeof geniusyield; hitbtc: typeof hitbtc; hitbtc3: typeof hitbtc3; hollaex: typeof hollaex; @@ -543,5 +546,5 @@ declare const ccxt: { zaif: typeof zaif; zonda: typeof zonda; } & typeof functions & typeof errors; -export { version, Exchange, exchanges, pro, Precise, functions, errors, BaseError, ExchangeError, AuthenticationError, PermissionDenied, AccountNotEnabled, AccountSuspended, ArgumentsRequired, BadRequest, BadSymbol, OperationRejected, NoChange, MarginModeAlreadySet, MarketClosed, BadResponse, NullResponse, InsufficientFunds, InvalidAddress, AddressPending, InvalidOrder, OrderNotFound, OrderNotCached, CancelPending, OrderImmediatelyFillable, OrderNotFillable, DuplicateOrderId, ContractUnavailable, NotSupported, ProxyError, ExchangeClosedByUser, OperationFailed, NetworkError, DDoSProtection, RateLimitExceeded, ExchangeNotAvailable, OnMaintenance, InvalidNonce, RequestTimeout, Int, int, Str, Strings, Num, Bool, IndexType, OrderSide, OrderType, MarketType, SubType, Dict, NullableDict, List, NullableList, Fee, OHLCV, OHLCVC, implicitReturnType, Market, Currency, Dictionary, MinMax, FeeInterface, TradingFeeInterface, MarketInterface, Trade, Order, OrderBook, Ticker, Transaction, Tickers, CurrencyInterface, Balance, BalanceAccount, Account, PartialBalances, Balances, DepositAddress, WithdrawalResponse, DepositAddressResponse, FundingRate, FundingRates, Position, BorrowInterest, LeverageTier, LedgerEntry, DepositWithdrawFeeNetwork, DepositWithdrawFee, TransferEntry, CrossBorrowRate, IsolatedBorrowRate, FundingRateHistory, OpenInterest, Liquidation, OrderRequest, CancellationRequest, FundingHistory, MarginMode, Greeks, Conversion, Option, LastPrice, Leverage, MarginModification, Leverages, LastPrices, Currencies, TradingFees, MarginModes, OptionChain, IsolatedBorrowRates, CrossBorrowRates, TransferEntries, LeverageTiers, ace, alpaca, ascendex, bequant, bigone, binance, binancecoinm, binanceus, binanceusdm, bingx, bit2c, bitbank, bitbay, bitbns, bitcoincom, bitfinex, bitfinex2, bitflyer, bitget, bithumb, bitmart, bitmex, bitopro, bitpanda, bitrue, bitso, bitstamp, bitteam, bitvavo, bl3p, blockchaincom, blofin, btcalpha, btcbox, btcmarkets, btcturk, bybit, cex, coinbase, coinbaseadvanced, coinbaseexchange, coinbaseinternational, coincheck, coinex, coinlist, coinmate, coinmetro, coinone, coinsph, coinspot, cryptocom, currencycom, delta, deribit, digifinex, exmo, fmfwio, gate, gateio, gemini, hitbtc, hitbtc3, hollaex, htx, huobi, huobijp, hyperliquid, idex, independentreserve, indodax, kraken, krakenfutures, kucoin, kucoinfutures, kuna, latoken, lbank, luno, lykke, mercado, mexc, ndax, novadax, oceanex, okcoin, okx, onetrading, oxfun, p2b, paymium, phemex, poloniex, poloniexfutures, probit, timex, tokocrypto, tradeogre, upbit, vertex, wavesexchange, wazirx, whitebit, woo, woofipro, xt, yobit, zaif, zonda, }; +export { version, Exchange, exchanges, pro, Precise, functions, errors, BaseError, ExchangeError, AuthenticationError, PermissionDenied, AccountNotEnabled, AccountSuspended, ArgumentsRequired, BadRequest, BadSymbol, OperationRejected, NoChange, MarginModeAlreadySet, MarketClosed, BadResponse, NullResponse, InsufficientFunds, InvalidAddress, AddressPending, InvalidOrder, OrderNotFound, OrderNotCached, CancelPending, OrderImmediatelyFillable, OrderNotFillable, DuplicateOrderId, ContractUnavailable, NotSupported, ProxyError, ExchangeClosedByUser, OperationFailed, NetworkError, DDoSProtection, RateLimitExceeded, ExchangeNotAvailable, OnMaintenance, InvalidNonce, RequestTimeout, Int, int, Str, Strings, Num, Bool, IndexType, OrderSide, OrderType, MarketType, SubType, Dict, NullableDict, List, NullableList, Fee, OHLCV, OHLCVC, implicitReturnType, Market, Currency, Dictionary, MinMax, FeeInterface, TradingFeeInterface, MarketInterface, Trade, Order, OrderBook, Ticker, Transaction, Tickers, CurrencyInterface, Balance, BalanceAccount, Account, PartialBalances, Balances, DepositAddress, WithdrawalResponse, DepositAddressResponse, FundingRate, FundingRates, Position, BorrowInterest, LeverageTier, LedgerEntry, DepositWithdrawFeeNetwork, DepositWithdrawFee, TransferEntry, CrossBorrowRate, IsolatedBorrowRate, FundingRateHistory, OpenInterest, Liquidation, OrderRequest, CancellationRequest, FundingHistory, MarginMode, Greeks, Conversion, Option, LastPrice, Leverage, MarginModification, Leverages, LastPrices, Currencies, TradingFees, MarginModes, OptionChain, IsolatedBorrowRates, CrossBorrowRates, TransferEntries, LeverageTiers, ace, alpaca, ascendex, bequant, bigone, binance, binancecoinm, binanceus, binanceusdm, bingx, bit2c, bitbank, bitbay, bitbns, bitcoincom, bitfinex, bitfinex2, bitflyer, bitget, bithumb, bitmart, bitmex, bitopro, bitpanda, bitrue, bitso, bitstamp, bitteam, bitvavo, bl3p, blockchaincom, blofin, btcalpha, btcbox, btcmarkets, btcturk, bybit, cex, coinbase, coinbaseadvanced, coinbaseexchange, coinbaseinternational, coincheck, coinex, coinlist, coinmate, coinmetro, coinone, coinsph, coinspot, cryptocom, currencycom, delta, deribit, digifinex, exmo, fmfwio, gate, gateio, gemini, geniusyield, hitbtc, hitbtc3, hollaex, htx, huobi, huobijp, hyperliquid, idex, independentreserve, indodax, kraken, krakenfutures, kucoin, kucoinfutures, kuna, latoken, lbank, luno, lykke, mercado, mexc, ndax, novadax, oceanex, okcoin, okx, onetrading, oxfun, p2b, paymium, phemex, poloniex, poloniexfutures, probit, timex, tokocrypto, tradeogre, upbit, vertex, wavesexchange, wazirx, whitebit, woo, woofipro, xt, yobit, zaif, zonda, }; export default ccxt; diff --git a/js/src/abstract/geniusyield.d.ts b/js/src/abstract/geniusyield.d.ts new file mode 100644 index 000000000000..62fd2c4c8bd3 --- /dev/null +++ b/js/src/abstract/geniusyield.d.ts @@ -0,0 +1,11 @@ +import { implicitReturnType } from '../base/types.js'; +import { Exchange as _Exchange } from '../base/Exchange.js'; +interface Exchange { + privateGetBalancesAddress(params?: {}): Promise; + privateGetMarkets(params?: {}): Promise; + privateGetTradingFees(params?: {}): Promise; + privateGetSettings(params?: {}): Promise; +} +declare abstract class Exchange extends _Exchange { +} +export default Exchange; diff --git a/js/src/abstract/geniusyield.js b/js/src/abstract/geniusyield.js new file mode 100644 index 000000000000..864db8b8baec --- /dev/null +++ b/js/src/abstract/geniusyield.js @@ -0,0 +1,11 @@ +// ---------------------------------------------------------------------------- + +// 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 +// EDIT THE CORRESPONDENT .ts FILE INSTEAD + +// ------------------------------------------------------------------------------- +import { Exchange as _Exchange } from '../base/Exchange.js'; +class Exchange extends _Exchange { +} +export default Exchange; diff --git a/js/src/geniusyield.d.ts b/js/src/geniusyield.d.ts new file mode 100644 index 000000000000..8c0eae7653fd --- /dev/null +++ b/js/src/geniusyield.d.ts @@ -0,0 +1,19 @@ +import Exchange from './abstract/geniusyield.js'; +import type { Balances, Market, MarketInterface, Str } from './base/types.js'; +/** + * @class geniusyield + * @augments Exchange + */ +export default class geniusyield extends Exchange { + safeMarket(marketId?: Str, market?: Market, delimiter?: Str, marketType?: Str): MarketInterface; + describe(): any; + fetchMarkets(params?: {}): Promise; + parseBalance(response: any): Balances; + fetchBalance(params?: {}): Promise; + sign(path: any, api?: string, method?: string, params?: {}, headers?: any, body?: any): { + url: string; + method: string; + body: any; + headers: any; + }; +} diff --git a/js/src/geniusyield.js b/js/src/geniusyield.js new file mode 100644 index 000000000000..e84a49dd8dd2 --- /dev/null +++ b/js/src/geniusyield.js @@ -0,0 +1,303 @@ +// ---------------------------------------------------------------------------- + +// 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 +// EDIT THE CORRESPONDENT .ts FILE INSTEAD + +// --------------------------------------------------------------------------- +import Exchange from './abstract/geniusyield.js'; +import { TICK_SIZE, PAD_WITH_ZERO } from './base/functions/number.js'; +import { InvalidOrder, InsufficientFunds, ExchangeNotAvailable, DDoSProtection, BadRequest, InvalidAddress, AuthenticationError } from './base/errors.js'; +// --------------------------------------------------------------------------- +/** + * @class geniusyield + * @augments Exchange + */ +export default class geniusyield extends Exchange { + safeMarket(marketId = undefined, market = undefined, delimiter = undefined, marketType = undefined) { + const isOption = (marketId !== undefined) && ((marketId.indexOf('-C') > -1) || (marketId.indexOf('-P') > -1)); + if (isOption && !(marketId in this.markets_by_id)) { + // handle expired option contracts + return this.createExpiredOptionMarket(marketId); + } + return super.safeMarket(marketId, market, delimiter, marketType); + } + describe() { + return this.deepExtend(super.describe(), { + 'id': 'geniusyield', + 'name': 'Genius Yield', + 'countries': ['CH'], + 'rateLimit': 1000, + 'version': 'v0', + 'pro': false, + 'dex': true, + 'certified': false, + 'requiresWeb3': true, + 'has': { + 'CORS': undefined, + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'addMargin': false, + 'cancelAllOrders': false, + 'cancelOrder': false, + 'cancelOrders': false, + 'closeAllPositions': false, + 'closePosition': false, + 'createDepositAddress': false, + 'createOrder': false, + 'createReduceOnlyOrder': false, + 'createStopLimitOrder': false, + 'createStopMarketOrder': false, + 'createStopOrder': false, + 'fetchBalance': true, + 'fetchBorrowRateHistories': false, + 'fetchBorrowRateHistory': false, + 'fetchClosedOrders': false, + 'fetchCrossBorrowRate': false, + 'fetchCrossBorrowRates': false, + 'fetchCurrencies': false, + 'fetchDeposit': false, + 'fetchDepositAddress': false, + 'fetchDepositAddresses': false, + 'fetchDepositAddressesByNetwork': false, + 'fetchDeposits': false, + 'fetchFundingHistory': false, + 'fetchFundingRate': false, + 'fetchFundingRateHistory': false, + 'fetchFundingRates': false, + 'fetchIndexOHLCV': false, + 'fetchIsolatedBorrowRate': false, + 'fetchIsolatedBorrowRates': false, + 'fetchLeverage': false, + 'fetchLeverageTiers': false, + 'fetchMarginMode': false, + 'fetchMarkets': true, + 'fetchMarkOHLCV': false, + 'fetchMyTrades': false, + 'fetchOHLCV': false, + 'fetchOpenInterestHistory': false, + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': false, + 'fetchOrders': false, + 'fetchPosition': false, + 'fetchPositionHistory': false, + 'fetchPositionMode': false, + 'fetchPositions': false, + 'fetchPositionsForSymbol': false, + 'fetchPositionsHistory': false, + 'fetchPositionsRisk': false, + 'fetchPremiumIndexOHLCV': false, + 'fetchStatus': false, + 'fetchTicker': false, + 'fetchTickers': false, + 'fetchTime': false, + 'fetchTrades': false, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransactions': false, + 'fetchWithdrawal': false, + 'fetchWithdrawals': false, + 'reduceMargin': false, + 'sandbox': false, + 'setLeverage': false, + 'setMarginMode': false, + 'setPositionMode': false, + 'transfer': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'api': { + 'preprod': 'http://localhost:8082', + 'mainnet': 'http://localhost:8082', + }, + 'www': 'https://www.geniusyield.co/', + 'doc': [ + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ], + }, + 'api': { + 'public': { + 'get': {}, + }, + 'private': { + 'get': { + 'balances/{address}': 10, + 'markets': 10, + 'trading-fees': 10, + 'settings': 10, + }, + }, + }, + 'headers': { + 'X-Gate-Channel-Id': 'ccxt', + }, + 'options': { + 'defaultTimeInForce': 'utc', + 'defaultSelfTradePrevention': 'cn', + 'network': 'mainnet', + }, + 'exceptions': { + 'exact': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + }, + 'requiredCredentials': { + 'walletAddress': false, + 'privateKey': false, + 'apiKey': true, + 'secret': false, + }, + 'precisionMode': TICK_SIZE, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + }); + } + async fetchMarkets(params = {}) { + this.checkRequiredCredentials(); + /** + * @method + * @name geniusyield#fetchMarkets + * @description retrieves data on all markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-markets + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} an array of objects representing market data + */ + const markets = await this.privateGetMarkets(params); + // [ + // { + // "market_id": "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + // "base_asset": "lovelace", + // "target_asset": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + // } + // ] + const fees = await this.privateGetTradingFees(); + // { + // "flat_maker_fee": "1000000", + // "flat_taker_fee": "1000000", + // "percentage_maker_fee": "0.3", + // "percentage_taker_fee": "0.3" + // } + const maker = this.safeNumber(fees, 'percentage_maker_fee'); + const taker = this.safeNumber(fees, 'percentage_taker_fee'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const entry = markets[i]; + const marketId = this.safeString(entry, 'market_id'); + const baseId = this.safeString(entry, 'base_asset'); + const quoteId = this.safeString(entry, 'target_asset'); + const base = this.safeCurrencyCode(baseId); + const quote = this.safeCurrencyCode(quoteId); + result.push({ + 'id': marketId, + 'symbol': marketId, + 'base': base, + 'quote': quote, + 'settle': undefined, + 'baseId': baseId, + 'quoteId': quoteId, + 'settleId': undefined, + 'type': 'spot', + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'active': true, + 'contract': false, + 'linear': undefined, + 'inverse': undefined, + 'taker': taker, + 'maker': maker, + 'contractSize': undefined, + 'expiry': undefined, + 'expiryDatetime': undefined, + 'strike': undefined, + 'optionType': undefined, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'leverage': { + 'min': undefined, + 'max': undefined, + }, + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'created': undefined, + 'info': entry, + }); + } + return result; + } + parseBalance(response) { + const result = { + 'info': response, + 'timestamp': undefined, + 'datetime': undefined, + }; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const currencyId = this.safeString(entry, 'asset'); + const code = this.safeCurrencyCode(currencyId); + const account = this.account(); + account['total'] = this.safeString(entry, 'quantity'); + account['free'] = this.safeString(entry, 'availableForTrade'); + account['used'] = this.safeString(entry, 'locked'); + result[code] = account; + } + return this.safeBalance(result); + } + async fetchBalance(params = {}) { + const settings = await this.privateGetSettings(params); + const address = this.safeString(settings, 'address'); + const request = { + 'address': address, + }; + const balances = await this.privateGetBalancesAddress(this.extend(request, params)); + return this.safeBalance(balances); + } + sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + this.checkRequiredCredentials(); + const network = this.safeString(this.options, 'network', 'mainnet'); + const version = this.safeString(this.options, 'version', 'v0'); + const url = this.urls['api'][network] + '/' + version + '/' + this.implodeParams(path, params); + headers = { + 'Content-Type': 'application/json', + }; + if (this.apiKey !== undefined) { + headers['api-key'] = this.apiKey; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +} diff --git a/php/abstract/geniusyield.php b/php/abstract/geniusyield.php new file mode 100644 index 000000000000..2680e5a36369 --- /dev/null +++ b/php/abstract/geniusyield.php @@ -0,0 +1,34 @@ +request('balances/{address}', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_markets($params = array()) { + return $this->request('markets', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_trading_fees($params = array()) { + return $this->request('trading-fees', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_settings($params = array()) { + return $this->request('settings', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetBalancesAddress($params = array()) { + return $this->request('balances/{address}', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetMarkets($params = array()) { + return $this->request('markets', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetTradingFees($params = array()) { + return $this->request('trading-fees', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetSettings($params = array()) { + return $this->request('settings', 'private', 'GET', $params, null, null, array("cost" => 10)); + } +} diff --git a/php/async/abstract/geniusyield.php b/php/async/abstract/geniusyield.php new file mode 100644 index 000000000000..9bf752c51074 --- /dev/null +++ b/php/async/abstract/geniusyield.php @@ -0,0 +1,34 @@ +request('balances/{address}', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_markets($params = array()) { + return $this->request('markets', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_trading_fees($params = array()) { + return $this->request('trading-fees', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function private_get_settings($params = array()) { + return $this->request('settings', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetBalancesAddress($params = array()) { + return $this->request('balances/{address}', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetMarkets($params = array()) { + return $this->request('markets', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetTradingFees($params = array()) { + return $this->request('trading-fees', 'private', 'GET', $params, null, null, array("cost" => 10)); + } + public function privateGetSettings($params = array()) { + return $this->request('settings', 'private', 'GET', $params, null, null, array("cost" => 10)); + } +} diff --git a/php/async/geniusyield.php b/php/async/geniusyield.php new file mode 100644 index 000000000000..666829fb9504 --- /dev/null +++ b/php/async/geniusyield.php @@ -0,0 +1,309 @@ + -1) || (mb_strpos($marketId, '-P') > -1)); + if ($isOption && !(is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id))) { + // handle expired option contracts + return $this->create_expired_option_market($marketId); + } + return parent::safe_market($marketId, $market, $delimiter, $marketType); + } + + public function describe() { + return $this->deep_extend(parent::describe(), array( + 'id' => 'geniusyield', + 'name' => 'Genius Yield', + 'countries' => array( 'CH' ), + 'rateLimit' => 1000, + 'version' => 'v0', + 'pro' => false, + 'dex' => true, + 'certified' => false, + 'requiresWeb3' => true, + 'has' => array( + 'CORS' => null, + 'spot' => true, + 'margin' => false, + 'swap' => false, + 'future' => false, + 'option' => false, + 'addMargin' => false, + 'cancelAllOrders' => false, + 'cancelOrder' => false, + 'cancelOrders' => false, + 'closeAllPositions' => false, + 'closePosition' => false, + 'createDepositAddress' => false, + 'createOrder' => false, + 'createReduceOnlyOrder' => false, + 'createStopLimitOrder' => false, + 'createStopMarketOrder' => false, + 'createStopOrder' => false, + 'fetchBalance' => true, + 'fetchBorrowRateHistories' => false, + 'fetchBorrowRateHistory' => false, + 'fetchClosedOrders' => false, + 'fetchCrossBorrowRate' => false, + 'fetchCrossBorrowRates' => false, + 'fetchCurrencies' => false, + 'fetchDeposit' => false, + 'fetchDepositAddress' => false, + 'fetchDepositAddresses' => false, + 'fetchDepositAddressesByNetwork' => false, + 'fetchDeposits' => false, + 'fetchFundingHistory' => false, + 'fetchFundingRate' => false, + 'fetchFundingRateHistory' => false, + 'fetchFundingRates' => false, + 'fetchIndexOHLCV' => false, + 'fetchIsolatedBorrowRate' => false, + 'fetchIsolatedBorrowRates' => false, + 'fetchLeverage' => false, + 'fetchLeverageTiers' => false, + 'fetchMarginMode' => false, + 'fetchMarkets' => true, + 'fetchMarkOHLCV' => false, + 'fetchMyTrades' => false, + 'fetchOHLCV' => false, + 'fetchOpenInterestHistory' => false, + 'fetchOpenOrders' => false, + 'fetchOrder' => false, + 'fetchOrderBook' => false, + 'fetchOrders' => false, + 'fetchPosition' => false, + 'fetchPositionHistory' => false, + 'fetchPositionMode' => false, + 'fetchPositions' => false, + 'fetchPositionsForSymbol' => false, + 'fetchPositionsHistory' => false, + 'fetchPositionsRisk' => false, + 'fetchPremiumIndexOHLCV' => false, + 'fetchStatus' => false, + 'fetchTicker' => false, + 'fetchTickers' => false, + 'fetchTime' => false, + 'fetchTrades' => false, + 'fetchTradingFee' => false, + 'fetchTradingFees' => false, + 'fetchTransactions' => false, + 'fetchWithdrawal' => false, + 'fetchWithdrawals' => false, + 'reduceMargin' => false, + 'sandbox' => false, + 'setLeverage' => false, + 'setMarginMode' => false, + 'setPositionMode' => false, + 'transfer' => false, + 'withdraw' => false, + ), + 'timeframes' => array( + '1m' => '1m', + '5m' => '5m', + '15m' => '15m', + '30m' => '30m', + '1h' => '1h', + '6h' => '6h', + '1d' => '1d', + ), + 'urls' => array( + 'api' => array( + 'preprod' => 'http://localhost:8082', + 'mainnet' => 'http://localhost:8082', + ), + 'www' => 'https://www.geniusyield.co/', + 'doc' => array( + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ), + ), + 'api' => array( + 'public' => array( + 'get' => array( + ), + ), + 'private' => array( + 'get' => array( + 'balances/{address}' => 10, + 'markets' => 10, + 'trading-fees' => 10, + 'settings' => 10, + ), + ), + ), + 'headers' => array( + 'X-Gate-Channel-Id' => 'ccxt', + ), + 'options' => array( + 'defaultTimeInForce' => 'utc', + 'defaultSelfTradePrevention' => 'cn', + 'network' => 'mainnet', + ), + 'exceptions' => array( + 'exact' => array( + 'INVALID_ORDER_QUANTITY' => '\\ccxt\\InvalidOrder', + 'INSUFFICIENT_FUNDS' => '\\ccxt\\InsufficientFunds', + 'SERVICE_UNAVAILABLE' => '\\ccxt\\ExchangeNotAvailable', + 'EXCEEDED_RATE_LIMIT' => '\\ccxt\\DDoSProtection', + 'INVALID_PARAMETER' => '\\ccxt\\BadRequest', + 'WALLET_NOT_ASSOCIATED' => '\\ccxt\\InvalidAddress', + 'INVALID_WALLET_SIGNATURE' => '\\ccxt\\AuthenticationError', + ), + ), + 'requiredCredentials' => array( + 'walletAddress' => false, + 'privateKey' => false, + 'apiKey' => true, + 'secret' => false, + ), + 'precisionMode' => TICK_SIZE, + 'paddingMode' => PAD_WITH_ZERO, + 'commonCurrencies' => array(), + )); + } + + public function fetch_markets($params = array ()): PromiseInterface { + return Async\async(function () use ($params) { + $this->check_required_credentials(); + /** + * retrieves data on all $markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-$markets + * @param {array} [$params] extra parameters specific to the exchange API endpoint + * @return {array[]} an array of objects representing market data + */ + $markets = Async\await($this->privateGetMarkets ($params)); + // array( + // { + // "market_id" => "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + // "base_asset" => "lovelace", + // "target_asset" => "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + // } + // ) + $fees = Async\await($this->privateGetTradingFees ()); + // { + // "flat_maker_fee" => "1000000", + // "flat_taker_fee" => "1000000", + // "percentage_maker_fee" => "0.3", + // "percentage_taker_fee" => "0.3" + // } + $maker = $this->safe_number($fees, 'percentage_maker_fee'); + $taker = $this->safe_number($fees, 'percentage_taker_fee'); + $result = array(); + for ($i = 0; $i < count($markets); $i++) { + $entry = $markets[$i]; + $marketId = $this->safe_string($entry, 'market_id'); + $baseId = $this->safe_string($entry, 'base_asset'); + $quoteId = $this->safe_string($entry, 'target_asset'); + $base = $this->safe_currency_code($baseId); + $quote = $this->safe_currency_code($quoteId); + $result[] = array( + 'id' => $marketId, + 'symbol' => $marketId, + 'base' => $base, + 'quote' => $quote, + 'settle' => null, + 'baseId' => $baseId, + 'quoteId' => $quoteId, + 'settleId' => null, + 'type' => 'spot', + 'spot' => true, + 'margin' => false, + 'swap' => false, + 'future' => false, + 'option' => false, + 'active' => true, + 'contract' => false, + 'linear' => null, + 'inverse' => null, + 'taker' => $taker, + 'maker' => $maker, + 'contractSize' => null, + 'expiry' => null, + 'expiryDatetime' => null, + 'strike' => null, + 'optionType' => null, + 'precision' => array( + 'amount' => null, + 'price' => null, + ), + 'limits' => array( + 'leverage' => array( + 'min' => null, + 'max' => null, + ), + 'amount' => array( + 'min' => null, + 'max' => null, + ), + 'price' => array( + 'min' => null, + 'max' => null, + ), + 'cost' => array( + 'min' => null, + 'max' => null, + ), + ), + 'created' => null, + 'info' => $entry, + ); + } + return $result; + }) (); + } + + public function parse_balance($response): array { + $result = array( + 'info' => $response, + 'timestamp' => null, + 'datetime' => null, + ); + for ($i = 0; $i < count($response); $i++) { + $entry = $response[$i]; + $currencyId = $this->safe_string($entry, 'asset'); + $code = $this->safe_currency_code($currencyId); + $account = $this->account(); + $account['total'] = $this->safe_string($entry, 'quantity'); + $account['free'] = $this->safe_string($entry, 'availableForTrade'); + $account['used'] = $this->safe_string($entry, 'locked'); + $result[$code] = $account; + } + return $this->safe_balance($result); + } + + public function fetch_balance($params = array ()): PromiseInterface { + return Async\async(function () use ($params) { + $settings = Async\await($this->privateGetSettings ($params)); + $address = $this->safe_string($settings, 'address'); + $request = array( + 'address' => $address, + ); + $balances = Async\await($this->privateGetBalancesAddress ($this->extend($request, $params))); + return $this->safe_balance($balances); + }) (); + } + + public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) { + $this->check_required_credentials(); + $network = $this->safe_string($this->options, 'network', 'mainnet'); + $version = $this->safe_string($this->options, 'version', 'v0'); + $url = $this->urls['api'][$network] . '/' . $version . '/' . $this->implode_params($path, $params); + $headers = array( + 'Content-Type' => 'application/json', + ); + if ($this->apiKey !== null) { + $headers['api-key'] = $this->apiKey; + } + return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers ); + } +} diff --git a/php/geniusyield.php b/php/geniusyield.php new file mode 100644 index 000000000000..9c4cf97d5b94 --- /dev/null +++ b/php/geniusyield.php @@ -0,0 +1,303 @@ + -1) || (mb_strpos($marketId, '-P') > -1)); + if ($isOption && !(is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id))) { + // handle expired option contracts + return $this->create_expired_option_market($marketId); + } + return parent::safe_market($marketId, $market, $delimiter, $marketType); + } + + public function describe() { + return $this->deep_extend(parent::describe(), array( + 'id' => 'geniusyield', + 'name' => 'Genius Yield', + 'countries' => array( 'CH' ), + 'rateLimit' => 1000, + 'version' => 'v0', + 'pro' => false, + 'dex' => true, + 'certified' => false, + 'requiresWeb3' => true, + 'has' => array( + 'CORS' => null, + 'spot' => true, + 'margin' => false, + 'swap' => false, + 'future' => false, + 'option' => false, + 'addMargin' => false, + 'cancelAllOrders' => false, + 'cancelOrder' => false, + 'cancelOrders' => false, + 'closeAllPositions' => false, + 'closePosition' => false, + 'createDepositAddress' => false, + 'createOrder' => false, + 'createReduceOnlyOrder' => false, + 'createStopLimitOrder' => false, + 'createStopMarketOrder' => false, + 'createStopOrder' => false, + 'fetchBalance' => true, + 'fetchBorrowRateHistories' => false, + 'fetchBorrowRateHistory' => false, + 'fetchClosedOrders' => false, + 'fetchCrossBorrowRate' => false, + 'fetchCrossBorrowRates' => false, + 'fetchCurrencies' => false, + 'fetchDeposit' => false, + 'fetchDepositAddress' => false, + 'fetchDepositAddresses' => false, + 'fetchDepositAddressesByNetwork' => false, + 'fetchDeposits' => false, + 'fetchFundingHistory' => false, + 'fetchFundingRate' => false, + 'fetchFundingRateHistory' => false, + 'fetchFundingRates' => false, + 'fetchIndexOHLCV' => false, + 'fetchIsolatedBorrowRate' => false, + 'fetchIsolatedBorrowRates' => false, + 'fetchLeverage' => false, + 'fetchLeverageTiers' => false, + 'fetchMarginMode' => false, + 'fetchMarkets' => true, + 'fetchMarkOHLCV' => false, + 'fetchMyTrades' => false, + 'fetchOHLCV' => false, + 'fetchOpenInterestHistory' => false, + 'fetchOpenOrders' => false, + 'fetchOrder' => false, + 'fetchOrderBook' => false, + 'fetchOrders' => false, + 'fetchPosition' => false, + 'fetchPositionHistory' => false, + 'fetchPositionMode' => false, + 'fetchPositions' => false, + 'fetchPositionsForSymbol' => false, + 'fetchPositionsHistory' => false, + 'fetchPositionsRisk' => false, + 'fetchPremiumIndexOHLCV' => false, + 'fetchStatus' => false, + 'fetchTicker' => false, + 'fetchTickers' => false, + 'fetchTime' => false, + 'fetchTrades' => false, + 'fetchTradingFee' => false, + 'fetchTradingFees' => false, + 'fetchTransactions' => false, + 'fetchWithdrawal' => false, + 'fetchWithdrawals' => false, + 'reduceMargin' => false, + 'sandbox' => false, + 'setLeverage' => false, + 'setMarginMode' => false, + 'setPositionMode' => false, + 'transfer' => false, + 'withdraw' => false, + ), + 'timeframes' => array( + '1m' => '1m', + '5m' => '5m', + '15m' => '15m', + '30m' => '30m', + '1h' => '1h', + '6h' => '6h', + '1d' => '1d', + ), + 'urls' => array( + 'api' => array( + 'preprod' => 'http://localhost:8082', + 'mainnet' => 'http://localhost:8082', + ), + 'www' => 'https://www.geniusyield.co/', + 'doc' => array( + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ), + ), + 'api' => array( + 'public' => array( + 'get' => array( + ), + ), + 'private' => array( + 'get' => array( + 'balances/{address}' => 10, + 'markets' => 10, + 'trading-fees' => 10, + 'settings' => 10, + ), + ), + ), + 'headers' => array( + 'X-Gate-Channel-Id' => 'ccxt', + ), + 'options' => array( + 'defaultTimeInForce' => 'utc', + 'defaultSelfTradePrevention' => 'cn', + 'network' => 'mainnet', + ), + 'exceptions' => array( + 'exact' => array( + 'INVALID_ORDER_QUANTITY' => '\\ccxt\\InvalidOrder', + 'INSUFFICIENT_FUNDS' => '\\ccxt\\InsufficientFunds', + 'SERVICE_UNAVAILABLE' => '\\ccxt\\ExchangeNotAvailable', + 'EXCEEDED_RATE_LIMIT' => '\\ccxt\\DDoSProtection', + 'INVALID_PARAMETER' => '\\ccxt\\BadRequest', + 'WALLET_NOT_ASSOCIATED' => '\\ccxt\\InvalidAddress', + 'INVALID_WALLET_SIGNATURE' => '\\ccxt\\AuthenticationError', + ), + ), + 'requiredCredentials' => array( + 'walletAddress' => false, + 'privateKey' => false, + 'apiKey' => true, + 'secret' => false, + ), + 'precisionMode' => TICK_SIZE, + 'paddingMode' => PAD_WITH_ZERO, + 'commonCurrencies' => array(), + )); + } + + public function fetch_markets($params = array ()): array { + $this->check_required_credentials(); + /** + * retrieves data on all $markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-$markets + * @param {array} [$params] extra parameters specific to the exchange API endpoint + * @return {array[]} an array of objects representing market data + */ + $markets = $this->privateGetMarkets ($params); + // array( + // { + // "market_id" => "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + // "base_asset" => "lovelace", + // "target_asset" => "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + // } + // ) + $fees = $this->privateGetTradingFees (); + // { + // "flat_maker_fee" => "1000000", + // "flat_taker_fee" => "1000000", + // "percentage_maker_fee" => "0.3", + // "percentage_taker_fee" => "0.3" + // } + $maker = $this->safe_number($fees, 'percentage_maker_fee'); + $taker = $this->safe_number($fees, 'percentage_taker_fee'); + $result = array(); + for ($i = 0; $i < count($markets); $i++) { + $entry = $markets[$i]; + $marketId = $this->safe_string($entry, 'market_id'); + $baseId = $this->safe_string($entry, 'base_asset'); + $quoteId = $this->safe_string($entry, 'target_asset'); + $base = $this->safe_currency_code($baseId); + $quote = $this->safe_currency_code($quoteId); + $result[] = array( + 'id' => $marketId, + 'symbol' => $marketId, + 'base' => $base, + 'quote' => $quote, + 'settle' => null, + 'baseId' => $baseId, + 'quoteId' => $quoteId, + 'settleId' => null, + 'type' => 'spot', + 'spot' => true, + 'margin' => false, + 'swap' => false, + 'future' => false, + 'option' => false, + 'active' => true, + 'contract' => false, + 'linear' => null, + 'inverse' => null, + 'taker' => $taker, + 'maker' => $maker, + 'contractSize' => null, + 'expiry' => null, + 'expiryDatetime' => null, + 'strike' => null, + 'optionType' => null, + 'precision' => array( + 'amount' => null, + 'price' => null, + ), + 'limits' => array( + 'leverage' => array( + 'min' => null, + 'max' => null, + ), + 'amount' => array( + 'min' => null, + 'max' => null, + ), + 'price' => array( + 'min' => null, + 'max' => null, + ), + 'cost' => array( + 'min' => null, + 'max' => null, + ), + ), + 'created' => null, + 'info' => $entry, + ); + } + return $result; + } + + public function parse_balance($response): array { + $result = array( + 'info' => $response, + 'timestamp' => null, + 'datetime' => null, + ); + for ($i = 0; $i < count($response); $i++) { + $entry = $response[$i]; + $currencyId = $this->safe_string($entry, 'asset'); + $code = $this->safe_currency_code($currencyId); + $account = $this->account(); + $account['total'] = $this->safe_string($entry, 'quantity'); + $account['free'] = $this->safe_string($entry, 'availableForTrade'); + $account['used'] = $this->safe_string($entry, 'locked'); + $result[$code] = $account; + } + return $this->safe_balance($result); + } + + public function fetch_balance($params = array ()): array { + $settings = $this->privateGetSettings ($params); + $address = $this->safe_string($settings, 'address'); + $request = array( + 'address' => $address, + ); + $balances = $this->privateGetBalancesAddress ($this->extend($request, $params)); + return $this->safe_balance($balances); + } + + public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) { + $this->check_required_credentials(); + $network = $this->safe_string($this->options, 'network', 'mainnet'); + $version = $this->safe_string($this->options, 'version', 'v0'); + $url = $this->urls['api'][$network] . '/' . $version . '/' . $this->implode_params($path, $params); + $headers = array( + 'Content-Type' => 'application/json', + ); + if ($this->apiKey !== null) { + $headers['api-key'] = $this->apiKey; + } + return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers ); + } +} diff --git a/python/ccxt/abstract/geniusyield.py b/python/ccxt/abstract/geniusyield.py new file mode 100644 index 000000000000..6d62bee80860 --- /dev/null +++ b/python/ccxt/abstract/geniusyield.py @@ -0,0 +1,8 @@ +from ccxt.base.types import Entry + + +class ImplicitAPI: + private_get_balances_address = privateGetBalancesAddress = Entry('balances/{address}', 'private', 'GET', {'cost': 10}) + private_get_markets = privateGetMarkets = Entry('markets', 'private', 'GET', {'cost': 10}) + private_get_trading_fees = privateGetTradingFees = Entry('trading-fees', 'private', 'GET', {'cost': 10}) + private_get_settings = privateGetSettings = Entry('settings', 'private', 'GET', {'cost': 10}) diff --git a/python/ccxt/async_support/geniusyield.py b/python/ccxt/async_support/geniusyield.py new file mode 100644 index 000000000000..ad2a4f2f48b2 --- /dev/null +++ b/python/ccxt/async_support/geniusyield.py @@ -0,0 +1,302 @@ +# -*- 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.async_support.base.exchange import Exchange +from ccxt.abstract.geniusyield import ImplicitAPI +from ccxt.base.types import Balances, Market, MarketInterface, Str +from typing import List +from ccxt.base.errors import AuthenticationError +from ccxt.base.errors import BadRequest +from ccxt.base.errors import InsufficientFunds +from ccxt.base.errors import InvalidAddress +from ccxt.base.errors import InvalidOrder +from ccxt.base.errors import DDoSProtection +from ccxt.base.errors import ExchangeNotAvailable +from ccxt.base.decimal_to_precision import TICK_SIZE +from ccxt.base.decimal_to_precision import PAD_WITH_ZERO + + +class geniusyield(Exchange, ImplicitAPI): + + def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface: + isOption = (marketId is not None) and ((marketId.find('-C') > -1) or (marketId.find('-P') > -1)) + if isOption and not (marketId in self.markets_by_id): + # handle expired option contracts + return self.create_expired_option_market(marketId) + return super(geniusyield, self).safe_market(marketId, market, delimiter, marketType) + + def describe(self): + return self.deep_extend(super(geniusyield, self).describe(), { + 'id': 'geniusyield', + 'name': 'Genius Yield', + 'countries': ['CH'], + 'rateLimit': 1000, + 'version': 'v0', + 'pro': False, + 'dex': True, + 'certified': False, + 'requiresWeb3': True, + 'has': { + 'CORS': None, + 'spot': True, + 'margin': False, + 'swap': False, + 'future': False, + 'option': False, + 'addMargin': False, + 'cancelAllOrders': False, + 'cancelOrder': False, + 'cancelOrders': False, + 'closeAllPositions': False, + 'closePosition': False, + 'createDepositAddress': False, + 'createOrder': False, + 'createReduceOnlyOrder': False, + 'createStopLimitOrder': False, + 'createStopMarketOrder': False, + 'createStopOrder': False, + 'fetchBalance': True, + 'fetchBorrowRateHistories': False, + 'fetchBorrowRateHistory': False, + 'fetchClosedOrders': False, + 'fetchCrossBorrowRate': False, + 'fetchCrossBorrowRates': False, + 'fetchCurrencies': False, + 'fetchDeposit': False, + 'fetchDepositAddress': False, + 'fetchDepositAddresses': False, + 'fetchDepositAddressesByNetwork': False, + 'fetchDeposits': False, + 'fetchFundingHistory': False, + 'fetchFundingRate': False, + 'fetchFundingRateHistory': False, + 'fetchFundingRates': False, + 'fetchIndexOHLCV': False, + 'fetchIsolatedBorrowRate': False, + 'fetchIsolatedBorrowRates': False, + 'fetchLeverage': False, + 'fetchLeverageTiers': False, + 'fetchMarginMode': False, + 'fetchMarkets': True, + 'fetchMarkOHLCV': False, + 'fetchMyTrades': False, + 'fetchOHLCV': False, + 'fetchOpenInterestHistory': False, + 'fetchOpenOrders': False, + 'fetchOrder': False, + 'fetchOrderBook': False, + 'fetchOrders': False, + 'fetchPosition': False, + 'fetchPositionHistory': False, + 'fetchPositionMode': False, + 'fetchPositions': False, + 'fetchPositionsForSymbol': False, + 'fetchPositionsHistory': False, + 'fetchPositionsRisk': False, + 'fetchPremiumIndexOHLCV': False, + 'fetchStatus': False, + 'fetchTicker': False, + 'fetchTickers': False, + 'fetchTime': False, + 'fetchTrades': False, + 'fetchTradingFee': False, + 'fetchTradingFees': False, + 'fetchTransactions': False, + 'fetchWithdrawal': False, + 'fetchWithdrawals': False, + 'reduceMargin': False, + 'sandbox': False, + 'setLeverage': False, + 'setMarginMode': False, + 'setPositionMode': False, + 'transfer': False, + 'withdraw': False, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'api': { + 'preprod': 'http://localhost:8082', + 'mainnet': 'http://localhost:8082', + }, + 'www': 'https://www.geniusyield.co/', + 'doc': [ + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ], + }, + 'api': { + 'public': { + 'get': { + }, + }, + 'private': { + 'get': { + 'balances/{address}': 10, + 'markets': 10, + 'trading-fees': 10, + 'settings': 10, + }, + }, + }, + 'headers': { + 'X-Gate-Channel-Id': 'ccxt', + }, + 'options': { + 'defaultTimeInForce': 'utc', + 'defaultSelfTradePrevention': 'cn', + 'network': 'mainnet', + }, + 'exceptions': { + 'exact': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + }, + 'requiredCredentials': { + 'walletAddress': False, + 'privateKey': False, + 'apiKey': True, + 'secret': False, + }, + 'precisionMode': TICK_SIZE, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + }) + + async def fetch_markets(self, params={}) -> List[Market]: + self.check_required_credentials() + """ + retrieves data on all markets for geniusyield + :see: https://api-docs-v3.geniusyield.io/#get-markets + :param dict [params]: extra parameters specific to the exchange API endpoint + :returns dict[]: an array of objects representing market data + """ + markets = await self.privateGetMarkets(params) + # [ + # { + # "market_id": "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + # "base_asset": "lovelace", + # "target_asset": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + # } + # ] + fees = await self.privateGetTradingFees() + # { + # "flat_maker_fee": "1000000", + # "flat_taker_fee": "1000000", + # "percentage_maker_fee": "0.3", + # "percentage_taker_fee": "0.3" + # } + maker = self.safe_number(fees, 'percentage_maker_fee') + taker = self.safe_number(fees, 'percentage_taker_fee') + result = [] + for i in range(0, len(markets)): + entry = markets[i] + marketId = self.safe_string(entry, 'market_id') + baseId = self.safe_string(entry, 'base_asset') + quoteId = self.safe_string(entry, 'target_asset') + base = self.safe_currency_code(baseId) + quote = self.safe_currency_code(quoteId) + result.append({ + 'id': marketId, + 'symbol': marketId, + 'base': base, + 'quote': quote, + 'settle': None, + 'baseId': baseId, + 'quoteId': quoteId, + 'settleId': None, + 'type': 'spot', + 'spot': True, + 'margin': False, + 'swap': False, + 'future': False, + 'option': False, + 'active': True, + 'contract': False, + 'linear': None, + 'inverse': None, + 'taker': taker, + 'maker': maker, + 'contractSize': None, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'precision': { + 'amount': None, + 'price': None, + }, + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': None, + 'max': None, + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, + }, + 'created': None, + 'info': entry, + }) + return result + + def parse_balance(self, response) -> Balances: + result: dict = { + 'info': response, + 'timestamp': None, + 'datetime': None, + } + for i in range(0, len(response)): + entry = response[i] + currencyId = self.safe_string(entry, 'asset') + code = self.safe_currency_code(currencyId) + account = self.account() + account['total'] = self.safe_string(entry, 'quantity') + account['free'] = self.safe_string(entry, 'availableForTrade') + account['used'] = self.safe_string(entry, 'locked') + result[code] = account + return self.safe_balance(result) + + async def fetch_balance(self, params={}) -> Balances: + settings = await self.privateGetSettings(params) + address = self.safe_string(settings, 'address') + request: dict = { + 'address': address, + } + balances = await self.privateGetBalancesAddress(self.extend(request, params)) + return self.safe_balance(balances) + + def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): + self.check_required_credentials() + network = self.safe_string(self.options, 'network', 'mainnet') + version = self.safe_string(self.options, 'version', 'v0') + url = self.urls['api'][network] + '/' + version + '/' + self.implode_params(path, params) + headers = { + 'Content-Type': 'application/json', + } + if self.apiKey is not None: + headers['api-key'] = self.apiKey + return {'url': url, 'method': method, 'body': body, 'headers': headers} diff --git a/python/ccxt/geniusyield.py b/python/ccxt/geniusyield.py new file mode 100644 index 000000000000..00ff29ded5d6 --- /dev/null +++ b/python/ccxt/geniusyield.py @@ -0,0 +1,302 @@ +# -*- 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.geniusyield import ImplicitAPI +from ccxt.base.types import Balances, Market, MarketInterface, Str +from typing import List +from ccxt.base.errors import AuthenticationError +from ccxt.base.errors import BadRequest +from ccxt.base.errors import InsufficientFunds +from ccxt.base.errors import InvalidAddress +from ccxt.base.errors import InvalidOrder +from ccxt.base.errors import DDoSProtection +from ccxt.base.errors import ExchangeNotAvailable +from ccxt.base.decimal_to_precision import TICK_SIZE +from ccxt.base.decimal_to_precision import PAD_WITH_ZERO + + +class geniusyield(Exchange, ImplicitAPI): + + def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface: + isOption = (marketId is not None) and ((marketId.find('-C') > -1) or (marketId.find('-P') > -1)) + if isOption and not (marketId in self.markets_by_id): + # handle expired option contracts + return self.create_expired_option_market(marketId) + return super(geniusyield, self).safe_market(marketId, market, delimiter, marketType) + + def describe(self): + return self.deep_extend(super(geniusyield, self).describe(), { + 'id': 'geniusyield', + 'name': 'Genius Yield', + 'countries': ['CH'], + 'rateLimit': 1000, + 'version': 'v0', + 'pro': False, + 'dex': True, + 'certified': False, + 'requiresWeb3': True, + 'has': { + 'CORS': None, + 'spot': True, + 'margin': False, + 'swap': False, + 'future': False, + 'option': False, + 'addMargin': False, + 'cancelAllOrders': False, + 'cancelOrder': False, + 'cancelOrders': False, + 'closeAllPositions': False, + 'closePosition': False, + 'createDepositAddress': False, + 'createOrder': False, + 'createReduceOnlyOrder': False, + 'createStopLimitOrder': False, + 'createStopMarketOrder': False, + 'createStopOrder': False, + 'fetchBalance': True, + 'fetchBorrowRateHistories': False, + 'fetchBorrowRateHistory': False, + 'fetchClosedOrders': False, + 'fetchCrossBorrowRate': False, + 'fetchCrossBorrowRates': False, + 'fetchCurrencies': False, + 'fetchDeposit': False, + 'fetchDepositAddress': False, + 'fetchDepositAddresses': False, + 'fetchDepositAddressesByNetwork': False, + 'fetchDeposits': False, + 'fetchFundingHistory': False, + 'fetchFundingRate': False, + 'fetchFundingRateHistory': False, + 'fetchFundingRates': False, + 'fetchIndexOHLCV': False, + 'fetchIsolatedBorrowRate': False, + 'fetchIsolatedBorrowRates': False, + 'fetchLeverage': False, + 'fetchLeverageTiers': False, + 'fetchMarginMode': False, + 'fetchMarkets': True, + 'fetchMarkOHLCV': False, + 'fetchMyTrades': False, + 'fetchOHLCV': False, + 'fetchOpenInterestHistory': False, + 'fetchOpenOrders': False, + 'fetchOrder': False, + 'fetchOrderBook': False, + 'fetchOrders': False, + 'fetchPosition': False, + 'fetchPositionHistory': False, + 'fetchPositionMode': False, + 'fetchPositions': False, + 'fetchPositionsForSymbol': False, + 'fetchPositionsHistory': False, + 'fetchPositionsRisk': False, + 'fetchPremiumIndexOHLCV': False, + 'fetchStatus': False, + 'fetchTicker': False, + 'fetchTickers': False, + 'fetchTime': False, + 'fetchTrades': False, + 'fetchTradingFee': False, + 'fetchTradingFees': False, + 'fetchTransactions': False, + 'fetchWithdrawal': False, + 'fetchWithdrawals': False, + 'reduceMargin': False, + 'sandbox': False, + 'setLeverage': False, + 'setMarginMode': False, + 'setPositionMode': False, + 'transfer': False, + 'withdraw': False, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'api': { + 'preprod': 'http://localhost:8082', + 'mainnet': 'http://localhost:8082', + }, + 'www': 'https://www.geniusyield.co/', + 'doc': [ + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ], + }, + 'api': { + 'public': { + 'get': { + }, + }, + 'private': { + 'get': { + 'balances/{address}': 10, + 'markets': 10, + 'trading-fees': 10, + 'settings': 10, + }, + }, + }, + 'headers': { + 'X-Gate-Channel-Id': 'ccxt', + }, + 'options': { + 'defaultTimeInForce': 'utc', + 'defaultSelfTradePrevention': 'cn', + 'network': 'mainnet', + }, + 'exceptions': { + 'exact': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + }, + 'requiredCredentials': { + 'walletAddress': False, + 'privateKey': False, + 'apiKey': True, + 'secret': False, + }, + 'precisionMode': TICK_SIZE, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + }) + + def fetch_markets(self, params={}) -> List[Market]: + self.check_required_credentials() + """ + retrieves data on all markets for geniusyield + :see: https://api-docs-v3.geniusyield.io/#get-markets + :param dict [params]: extra parameters specific to the exchange API endpoint + :returns dict[]: an array of objects representing market data + """ + markets = self.privateGetMarkets(params) + # [ + # { + # "market_id": "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + # "base_asset": "lovelace", + # "target_asset": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + # } + # ] + fees = self.privateGetTradingFees() + # { + # "flat_maker_fee": "1000000", + # "flat_taker_fee": "1000000", + # "percentage_maker_fee": "0.3", + # "percentage_taker_fee": "0.3" + # } + maker = self.safe_number(fees, 'percentage_maker_fee') + taker = self.safe_number(fees, 'percentage_taker_fee') + result = [] + for i in range(0, len(markets)): + entry = markets[i] + marketId = self.safe_string(entry, 'market_id') + baseId = self.safe_string(entry, 'base_asset') + quoteId = self.safe_string(entry, 'target_asset') + base = self.safe_currency_code(baseId) + quote = self.safe_currency_code(quoteId) + result.append({ + 'id': marketId, + 'symbol': marketId, + 'base': base, + 'quote': quote, + 'settle': None, + 'baseId': baseId, + 'quoteId': quoteId, + 'settleId': None, + 'type': 'spot', + 'spot': True, + 'margin': False, + 'swap': False, + 'future': False, + 'option': False, + 'active': True, + 'contract': False, + 'linear': None, + 'inverse': None, + 'taker': taker, + 'maker': maker, + 'contractSize': None, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'precision': { + 'amount': None, + 'price': None, + }, + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': None, + 'max': None, + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, + }, + 'created': None, + 'info': entry, + }) + return result + + def parse_balance(self, response) -> Balances: + result: dict = { + 'info': response, + 'timestamp': None, + 'datetime': None, + } + for i in range(0, len(response)): + entry = response[i] + currencyId = self.safe_string(entry, 'asset') + code = self.safe_currency_code(currencyId) + account = self.account() + account['total'] = self.safe_string(entry, 'quantity') + account['free'] = self.safe_string(entry, 'availableForTrade') + account['used'] = self.safe_string(entry, 'locked') + result[code] = account + return self.safe_balance(result) + + def fetch_balance(self, params={}) -> Balances: + settings = self.privateGetSettings(params) + address = self.safe_string(settings, 'address') + request: dict = { + 'address': address, + } + balances = self.privateGetBalancesAddress(self.extend(request, params)) + return self.safe_balance(balances) + + def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): + self.check_required_credentials() + network = self.safe_string(self.options, 'network', 'mainnet') + version = self.safe_string(self.options, 'version', 'v0') + url = self.urls['api'][network] + '/' + version + '/' + self.implode_params(path, params) + headers = { + 'Content-Type': 'application/json', + } + if self.apiKey is not None: + headers['api-key'] = self.apiKey + return {'url': url, 'method': method, 'body': body, 'headers': headers} diff --git a/ts/src/abstract/geniusyield.ts b/ts/src/abstract/geniusyield.ts new file mode 100644 index 000000000000..1d6a8062086a --- /dev/null +++ b/ts/src/abstract/geniusyield.ts @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------------- + +// 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 + +// ------------------------------------------------------------------------------- + +import { implicitReturnType } from '../base/types.js'; +import { Exchange as _Exchange } from '../base/Exchange.js'; + +interface Exchange { + privateGetBalancesAddress (params?: {}): Promise; + privateGetMarkets (params?: {}): Promise; + privateGetTradingFees (params?: {}): Promise; + privateGetSettings (params?: {}): Promise; +} +abstract class Exchange extends _Exchange {} + +export default Exchange diff --git a/ts/src/geniusyield.ts b/ts/src/geniusyield.ts new file mode 100644 index 000000000000..236fba13cfbf --- /dev/null +++ b/ts/src/geniusyield.ts @@ -0,0 +1,308 @@ + +// --------------------------------------------------------------------------- + +import Exchange from './abstract/geniusyield.js'; +import { TICK_SIZE, PAD_WITH_ZERO } from './base/functions/number.js'; +import { InvalidOrder, InsufficientFunds, ExchangeNotAvailable, DDoSProtection, BadRequest, InvalidAddress, AuthenticationError } from './base/errors.js'; +import type { Balances, Dict, Market, MarketInterface, Str } from './base/types.js'; + +// --------------------------------------------------------------------------- + +/** + * @class geniusyield + * @augments Exchange + */ +export default class geniusyield extends Exchange { + safeMarket (marketId: Str = undefined, market: Market = undefined, delimiter: Str = undefined, marketType: Str = undefined): MarketInterface { + const isOption = (marketId !== undefined) && ((marketId.indexOf ('-C') > -1) || (marketId.indexOf ('-P') > -1)); + if (isOption && !(marketId in this.markets_by_id)) { + // handle expired option contracts + return this.createExpiredOptionMarket (marketId); + } + return super.safeMarket (marketId, market, delimiter, marketType); + } + + describe () { + return this.deepExtend (super.describe (), { + 'id': 'geniusyield', + 'name': 'Genius Yield', + 'countries': [ 'CH' ], + 'rateLimit': 1000, + 'version': 'v0', + 'pro': false, + 'dex': true, + 'certified': false, + 'requiresWeb3': true, + 'has': { + 'CORS': undefined, + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'addMargin': false, + 'cancelAllOrders': false, + 'cancelOrder': false, + 'cancelOrders': false, + 'closeAllPositions': false, + 'closePosition': false, + 'createDepositAddress': false, + 'createOrder': false, + 'createReduceOnlyOrder': false, + 'createStopLimitOrder': false, + 'createStopMarketOrder': false, + 'createStopOrder': false, + 'fetchBalance': true, + 'fetchBorrowRateHistories': false, + 'fetchBorrowRateHistory': false, + 'fetchClosedOrders': false, + 'fetchCrossBorrowRate': false, + 'fetchCrossBorrowRates': false, + 'fetchCurrencies': false, + 'fetchDeposit': false, + 'fetchDepositAddress': false, + 'fetchDepositAddresses': false, + 'fetchDepositAddressesByNetwork': false, + 'fetchDeposits': false, + 'fetchFundingHistory': false, + 'fetchFundingRate': false, + 'fetchFundingRateHistory': false, + 'fetchFundingRates': false, + 'fetchIndexOHLCV': false, + 'fetchIsolatedBorrowRate': false, + 'fetchIsolatedBorrowRates': false, + 'fetchLeverage': false, + 'fetchLeverageTiers': false, + 'fetchMarginMode': false, + 'fetchMarkets': true, + 'fetchMarkOHLCV': false, + 'fetchMyTrades': false, + 'fetchOHLCV': false, + 'fetchOpenInterestHistory': false, + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': false, + 'fetchOrders': false, + 'fetchPosition': false, + 'fetchPositionHistory': false, + 'fetchPositionMode': false, + 'fetchPositions': false, + 'fetchPositionsForSymbol': false, + 'fetchPositionsHistory': false, + 'fetchPositionsRisk': false, + 'fetchPremiumIndexOHLCV': false, + 'fetchStatus': false, + 'fetchTicker': false, + 'fetchTickers': false, + 'fetchTime': false, + 'fetchTrades': false, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransactions': false, + 'fetchWithdrawal': false, + 'fetchWithdrawals': false, + 'reduceMargin': false, + 'sandbox': false, + 'setLeverage': false, + 'setMarginMode': false, + 'setPositionMode': false, + 'transfer': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'api': { + 'preprod': 'http://localhost:8082', + 'mainnet': 'http://localhost:8082', + }, + 'www': 'https://www.geniusyield.co/', + 'doc': [ + 'https://github.com/geniusyield/dex-contracts-api?tab=readme-ov-file#geniusyield-dex', + ], + }, + 'api': { + 'public': { + 'get': { + }, + }, + 'private': { + 'get': { + 'balances/{address}': 10, + 'markets': 10, + 'trading-fees': 10, + 'settings': 10, + }, + }, + }, + 'headers': { + 'X-Gate-Channel-Id': 'ccxt', + }, + 'options': { + 'defaultTimeInForce': 'utc', + 'defaultSelfTradePrevention': 'cn', + 'network': 'mainnet', + }, + 'exceptions': { + 'exact': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + }, + 'requiredCredentials': { + 'walletAddress': false, + 'privateKey': false, + 'apiKey': true, + 'secret': false, + }, + 'precisionMode': TICK_SIZE, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + }); + } + + async fetchMarkets (params = {}): Promise { + this.checkRequiredCredentials (); + /** + * @method + * @name geniusyield#fetchMarkets + * @description retrieves data on all markets for geniusyield + * @see https://api-docs-v3.geniusyield.io/#get-markets + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} an array of objects representing market data + */ + const markets = await this.privateGetMarkets (params); + // [ + // { + // "market_id": "lovelace_dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", + // "base_asset": "lovelace", + // "target_asset": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" + // } + // ] + const fees = await this.privateGetTradingFees (); + // { + // "flat_maker_fee": "1000000", + // "flat_taker_fee": "1000000", + // "percentage_maker_fee": "0.3", + // "percentage_taker_fee": "0.3" + // } + const maker = this.safeNumber (fees, 'percentage_maker_fee'); + const taker = this.safeNumber (fees, 'percentage_taker_fee'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const entry = markets[i]; + const marketId = this.safeString (entry, 'market_id'); + const baseId = this.safeString (entry, 'base_asset'); + const quoteId = this.safeString (entry, 'target_asset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + result.push ({ + 'id': marketId, + 'symbol': marketId, + 'base': base, + 'quote': quote, + 'settle': undefined, + 'baseId': baseId, + 'quoteId': quoteId, + 'settleId': undefined, + 'type': 'spot', + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'active': true, + 'contract': false, + 'linear': undefined, + 'inverse': undefined, + 'taker': taker, + 'maker': maker, + 'contractSize': undefined, + 'expiry': undefined, + 'expiryDatetime': undefined, + 'strike': undefined, + 'optionType': undefined, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'leverage': { + 'min': undefined, + 'max': undefined, + }, + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'created': undefined, + 'info': entry, + }); + } + return result; + } + + parseBalance (response): Balances { + const result: Dict = { + 'info': response, + 'timestamp': undefined, + 'datetime': undefined, + }; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const currencyId = this.safeString (entry, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeString (entry, 'quantity'); + account['free'] = this.safeString (entry, 'availableForTrade'); + account['used'] = this.safeString (entry, 'locked'); + result[code] = account; + } + return this.safeBalance (result); + } + + async fetchBalance (params = {}): Promise { + const settings = await this.privateGetSettings (params); + const address = this.safeString (settings, 'address'); + const request: Dict = { + 'address': address, + }; + const balances = await this.privateGetBalancesAddress (this.extend (request, params)); + return this.safeBalance (balances); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + this.checkRequiredCredentials (); + const network = this.safeString (this.options, 'network', 'mainnet'); + const version = this.safeString (this.options, 'version', 'v0'); + const url = this.urls['api'][network] + '/' + version + '/' + this.implodeParams (path, params); + headers = { + 'Content-Type': 'application/json', + }; + if (this.apiKey !== undefined) { + headers['api-key'] = this.apiKey; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +} diff --git a/wiki/exchanges/geniusyield.md b/wiki/exchanges/geniusyield.md new file mode 100644 index 000000000000..d15034974284 --- /dev/null +++ b/wiki/exchanges/geniusyield.md @@ -0,0 +1,28 @@ + + + +## geniusyield{docsify-ignore} +**Kind**: global class +**Extends**: Exchange + +* [fetchMarkets](#fetchmarkets) + + + +### fetchMarkets{docsify-ignore} +retrieves data on all markets for geniusyield + +**Kind**: instance method of [geniusyield](#geniusyield) +**Returns**: Array<object> - an array of objects representing market data + +**See**: https://api-docs-v3.geniusyield.io/#get-markets + +| Param | Type | Required | Description | +| --- | --- | --- | --- | +| params | object | No | extra parameters specific to the exchange API endpoint | + + +```javascript +geniusyield.fetchMarkets ([params]) +``` +