@hydro-protocol/hydro-client-js
Version:
Javascript SDK for the Hydro API
691 lines (690 loc) • 30.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var bignumber_js_1 = __importDefault(require("bignumber.js"));
var ethereumjs_tx_1 = __importDefault(require("ethereumjs-tx"));
var ethereumjs_util_1 = require("ethereumjs-util");
var path_1 = require("path");
var ApiHandler_1 = require("./ApiHandler");
var Web3Handler_1 = require("./Web3Handler");
var errors_1 = require("../errors/errors");
var Candle_1 = require("../models/Candle");
var Fee_1 = require("../models/Fee");
var LockedBalance_1 = require("../models/LockedBalance");
var Market_1 = require("../models/Market");
var Order_1 = require("../models/Order");
var Orderbook_1 = require("../models/Orderbook");
var OrderList_1 = require("../models/OrderList");
var Ticker_1 = require("../models/Ticker");
var TradeList_1 = require("../models/TradeList");
var HydroClient = /** @class */ (function () {
function HydroClient(account, options) {
this.account = account;
this.apiHandler = new ApiHandler_1.ApiHandler(account, options);
this.web3Handler = new Web3Handler_1.Web3Handler(account, options);
this.tokenAddresses = new Map();
}
/**
* If you only want to make public API calls, no authentication is needed
*/
HydroClient.withoutAuth = function (options) {
var errorFn = function (_) {
throw new errors_1.AuthError('Cannot authenticate without a private key!');
};
var account = {
address: '',
sign: errorFn,
signTransaction: errorFn,
};
return new HydroClient(account, options);
};
/**
* Provide a private key for authentication purposes
* @param privateKey A private key in hex format with the form "0x..."
*/
HydroClient.withPrivateKey = function (privateKey, options) {
var _this = this;
var pkBuffer = ethereumjs_util_1.toBuffer(privateKey);
var address = '0x' + ethereumjs_util_1.privateToAddress(pkBuffer).toString('hex');
var sign = function (message) { return __awaiter(_this, void 0, void 0, function () {
var shaMessage, ecdsaSignature;
return __generator(this, function (_a) {
shaMessage = ethereumjs_util_1.hashPersonalMessage(ethereumjs_util_1.toBuffer(message));
ecdsaSignature = ethereumjs_util_1.ecsign(shaMessage, pkBuffer);
return [2 /*return*/, ethereumjs_util_1.toRpcSig(ecdsaSignature.v, ecdsaSignature.r, ecdsaSignature.s)];
});
}); };
var signTransaction = function (txParams) { return __awaiter(_this, void 0, void 0, function () {
var tx;
return __generator(this, function (_a) {
tx = new ethereumjs_tx_1.default(txParams);
tx.sign(pkBuffer);
return [2 /*return*/, '0x' + tx.serialize().toString('hex')];
});
}); };
return new HydroClient({ address: address, sign: sign, signTransaction: signTransaction }, options);
};
/**
* If you don't want to supply your private key, or want to integrate with a wallet, provide
* your own function to sign messages and the account you will be using.
*
* @param address The address of the account that will be doing the signing
* @param sign A function that takes the input message and signs it with the private key of the account
* @param signTransaction An async function that takes a transaction object and signs it with the private key of the account
*/
HydroClient.withCustomAuth = function (address, sign, signTransaction, options) {
return new HydroClient({ address: address, sign: sign, signTransaction: signTransaction }, options);
};
/**
* Public API Calls
*
* These calls do not require any authentication to complete, and will generally give you
* public state about the Hydro API
*
* See https://docs.ddex.io/#public-rest-api
*/
/**
* Returns all active markets
*
* See https://docs.ddex.io/#list-markets
*/
HydroClient.prototype.listMarkets = function () {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets'))];
case 1:
data = _a.sent();
return [2 /*return*/, data.markets.map(function (market) { return new Market_1.Market(market); })];
}
});
});
};
/**
* Returns a specific market
*
* See https://docs.ddex.io/#get-a-market
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
*/
HydroClient.prototype.getMarket = function (marketId) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId))];
case 1:
data = _a.sent();
return [2 /*return*/, new Market_1.Market(data.market)];
}
});
});
};
/**
* Returns tickers for all active markets
*
* See https://docs.ddex.io/#list-tickers
*/
HydroClient.prototype.listTickers = function () {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', 'tickers'))];
case 1:
data = _a.sent();
return [2 /*return*/, data.tickers.map(function (ticker) { return new Ticker_1.Ticker(ticker); })];
}
});
});
};
/**
* Returns ticker for a specific market
*
* See https://docs.ddex.io/#get-a-ticker
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
*/
HydroClient.prototype.getTicker = function (marketId) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId, 'ticker'))];
case 1:
data = _a.sent();
return [2 /*return*/, new Ticker_1.Ticker(data.ticker)];
}
});
});
};
/**
* Returns the orderbook for a specific market
*
* See https://docs.ddex.io/#get-orderbook
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param level (Optional) The amount of detail returned in the orderbook. Default is level ONE.
*/
HydroClient.prototype.getOrderbook = function (marketId, level) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId, 'orderbook'), {
level: level,
})];
case 1:
data = _a.sent();
return [2 /*return*/, new Orderbook_1.Orderbook(data.orderBook, level)];
}
});
});
};
/**
* Returns paginated trades for a specific market
*
* See https://docs.ddex.io/#get-trades
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param page (Optional) Which page to return. Default is page 1.
* @param perPage (Optional) How many results per page. Default is 20.
*/
HydroClient.prototype.listTrades = function (marketId, page, perPage) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId, 'trades'), {
page: page,
perPage: perPage,
})];
case 1:
data = _a.sent();
return [2 /*return*/, new TradeList_1.TradeList(data)];
}
});
});
};
/**
* Returns "candles" for building a trading chart for a specific market
*
* See https://docs.ddex.io/#get-candles
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param from The beginning of the time range as a UNIX timestamp
* @param to The end of the time range as a UNIX timestamp
* @param granularity The width of each candle in seconds
*/
HydroClient.prototype.listCandles = function (marketId, from, to, granularity) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId, 'candles'), {
from: from,
to: to,
granularity: granularity,
})];
case 1:
data = _a.sent();
return [2 /*return*/, data.candles.map(function (candle) { return new Candle_1.Candle(candle); })];
}
});
});
};
/**
* Calculate an estimated fee taken by the exchange given a price and amount for an order
*
* See https://docs.ddex.io/#calculate-fees
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param price The price of the order
* @param amount The amount of token in the order
*/
HydroClient.prototype.calculateFees = function (marketId, price, amount) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('fees'), {
marketId: marketId,
price: price,
amount: amount,
})];
case 1:
data = _a.sent();
return [2 /*return*/, new Fee_1.Fee(data)];
}
});
});
};
/**
* Private API Calls
*
* These calls require authentication, meaning you must have a valid trading address
* and the ability to sign requests with that address' private key.
*
* See https://docs.ddex.io/#private-rest-api
*/
/**
* Build a new order to submit to the exchange
*
* See https://docs.ddex.io/#build-unsigned-order
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param side Whether this is a "buy" or "sell" order
* @param orderType Whether this is a "limit" or "market" order
* @param price The price of the order
* @param amount The amount of token in the order
* @param expires (Optional) A number of seconds after which this order will expire. Defaults to 0 (no expiration).
*/
HydroClient.prototype.buildOrder = function (marketId, side, orderType, price, amount, expires) {
if (expires === void 0) { expires = 0; }
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.post(path_1.join('orders', 'build'), {
marketId: marketId,
side: side,
orderType: orderType,
price: price,
amount: amount,
expires: expires,
})];
case 1:
data = _a.sent();
return [2 /*return*/, new Order_1.Order(data.order)];
}
});
});
};
/**
* Submit a signed order to the exchange
*
* See https://docs.ddex.io/#place-order
*
* @param orderId The id of a built order
* @param signature String created by signing the orderId
*/
HydroClient.prototype.placeOrder = function (orderId, signature) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.post(path_1.join('orders'), {
orderId: orderId,
signature: signature,
method: SignatureMethod.ETH_SIGN,
})];
case 1:
data = _a.sent();
return [2 /*return*/, new Order_1.Order(data.order)];
}
});
});
};
/**
* A convenience function that will build an order, sign the order, and then
* immediately place the order on the system using the signing method passed
* in.
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param side Whether this is a "buy" or "sell" order
* @param orderType Whether this is a "limit" or "market" order
* @param price The price of the order
* @param amount The amount of token in the order
* @param expires (Optional) A number of seconds after which this order will expire. Defaults to 0 (no expiration).
*/
HydroClient.prototype.createOrder = function (marketId, side, orderType, price, amount, expires) {
if (expires === void 0) { expires = 0; }
return __awaiter(this, void 0, void 0, function () {
var order, signature;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.buildOrder(marketId, side, orderType, price, amount, expires)];
case 1:
order = _a.sent();
return [4 /*yield*/, this.account.sign(order.id)];
case 2:
signature = _a.sent();
return [4 /*yield*/, this.placeOrder(order.id, signature)];
case 3: return [2 /*return*/, _a.sent()];
}
});
});
};
/**
* Cancel an order you have submitted to the exchange
*
* See https://docs.ddex.io/#cancel-order
*
* @param orderId The id of the order you wish to cancel
*/
HydroClient.prototype.cancelOrder = function (orderId) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.delete(path_1.join('orders', orderId))];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* Return paginated orders you have submitted to the exchange
*
* See https://docs.ddex.io/#list-orders
*
* @param marketId (Optional) The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param status (Optional) Choose between "pending" or "all" orders
* @param page (Optional) Which page to return. Default is page 1.
* @param perPage (Optional) How many results per page. Default is 20.
*/
HydroClient.prototype.listOrders = function (marketId, status, page, perPage) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('orders'), { marketId: marketId, status: status, page: page, perPage: perPage }, true)];
case 1:
data = _a.sent();
return [2 /*return*/, new OrderList_1.OrderList(data)];
}
});
});
};
/**
* Return a specific order you have submitted to the exchange
*
* See https://docs.ddex.io/#get-order
*
* @param orderId The id of the order
*/
HydroClient.prototype.getOrder = function (orderId) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('orders', orderId), {}, true)];
case 1:
data = _a.sent();
return [2 /*return*/, new Order_1.Order(data.order)];
}
});
});
};
/**
* Return paginated list of all trades you have made
*
* See https://docs.ddex.io/#list-account-trades
*
* @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH"
* @param page (Optional) Which page to return. Default is page 1.
* @param perPage (Optional) How many results per page. Default is 20.
*/
HydroClient.prototype.listAccountTrades = function (marketId, page, perPage) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('markets', marketId, 'trades', 'mine'), { page: page, perPage: perPage }, true)];
case 1:
data = _a.sent();
return [2 /*return*/, new TradeList_1.TradeList(data)];
}
});
});
};
/**
* Return locked balances for each active token
*
* See https://docs.ddex.io/#list-locked-balances
*/
HydroClient.prototype.listLockedBalances = function () {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('account', 'lockedBalances'), {}, true)];
case 1:
data = _a.sent();
return [2 /*return*/, data.lockedBalances.map(function (lockedBalance) { return new LockedBalance_1.LockedBalance(lockedBalance); })];
}
});
});
};
/**
* Return a specific locked balance
*
* See https://docs.ddex.io/#get-locked-balance
*
* @param symbol The symbol for the token you want to see your locked balance
*/
HydroClient.prototype.getLockedBalance = function (symbol) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.apiHandler.get(path_1.join('account', 'lockedBalance'), { symbol: symbol }, true)];
case 1:
data = _a.sent();
return [2 /*return*/, new LockedBalance_1.LockedBalance(data.lockedBalance)];
}
});
});
};
/**
* Helper Methods (requires auth)
*
* These helper methods don't generally don't call the Hydro API, instead querying the blockchain
* directly. They are useful in helping to wrap/unwrap ETH on your account, and allowing you to
* approve tokens to be traded on the DDEX-1.0 relayer.
*
* To use these methods, you must provide a mainnet endpoint url, like infura, which will be used
* to interact with the blockchain. It is taken in as one of the HydroClient options, as web3_url.
*
* See
* * https://docs.ddex.io/#wrapping-ether
* * https://docs.ddex.io/#enabling-token-trading
*/
/**
* Query your balance of a token.
*
* @param symbol Symbol of a token you wish to query the balance of. No token returns ETH balance.
* @return Balance in your account for this token.
*/
HydroClient.prototype.getBalance = function (symbol) {
return __awaiter(this, void 0, void 0, function () {
var address;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!symbol) return [3 /*break*/, 2];
return [4 /*yield*/, this.getTokenAddress(symbol)];
case 1:
address = _a.sent();
_a.label = 2;
case 2: return [2 /*return*/, this.web3Handler.getBalance(address)];
}
});
});
};
/**
* Wrap a specified amount of ETH from your account into WETH. This is required because the
* DDEX-1.0 relayer can only perform atomic trading between two ERC20 tokens, and unfortunately
* ETH itself does not conform to the ERC20 standard. ETH and WETH are always exchanged at a 1:1
* ratio, so you can wrap and unwrap ETH anytime you like with only the cost of gas.
*
* @param amount The amount of ETH to wrap
* @param wait If true, the promise will only resolve when the transaction is confirmed
* @return Transaction hash
*/
HydroClient.prototype.wrapEth = function (amount, wait) {
return __awaiter(this, void 0, void 0, function () {
var wethAddress;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getTokenAddress('WETH')];
case 1:
wethAddress = _a.sent();
return [2 /*return*/, this.web3Handler.wrapEth(wethAddress, amount, wait)];
}
});
});
};
/**
* Unwrap a specified amount of WETH from your account back into ETH.
*
* @param amount The amount of WETH to unwrap
* @param wait If true, the promise will only resolve when the transaction is confirmed
* @return Transaction hash
*/
HydroClient.prototype.unwrapEth = function (amount, wait) {
return __awaiter(this, void 0, void 0, function () {
var wethAddress;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getTokenAddress('WETH')];
case 1:
wethAddress = _a.sent();
return [2 /*return*/, this.web3Handler.unwrapEth(wethAddress, amount, wait)];
}
});
});
};
/**
* Determine if this token has a proxy allowance set on the Hydro proxy contract.
*
* @param symbol Symbol of a token you wish to check if it is enabled or diabled for sale.
*/
HydroClient.prototype.isTokenEnabled = function (symbol) {
return __awaiter(this, void 0, void 0, function () {
var tokenAddress, allowance;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getTokenAddress(symbol)];
case 1:
tokenAddress = _a.sent();
return [4 /*yield*/, this.web3Handler.getAllowance(tokenAddress)];
case 2:
allowance = _a.sent();
return [2 /*return*/, new bignumber_js_1.default(allowance).gte(new bignumber_js_1.default(10).pow(10))];
}
});
});
};
/**
* Enable token to be sold via Hydro API. This will allow the Hydro proxy contract to send tokens
* of this type on your behalf, allowing atomic trading of tokens between two parties.
*
* @param symbol Symbol of a token you wish to enable for sale via Hydro API
* @param wait If true, the promise will only resolve when the transaction is confirmed
* @return Transaction hash
*/
HydroClient.prototype.enableToken = function (symbol, wait) {
return __awaiter(this, void 0, void 0, function () {
var tokenAddress;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getTokenAddress(symbol)];
case 1:
tokenAddress = _a.sent();
return [2 /*return*/, this.web3Handler.enableToken(tokenAddress, wait)];
}
});
});
};
/**
* Disable token to be sold via Hydro API. The Hydro proxy contract will no longer be able to send
* tokens of this type on your behalf.
*
* @param symbol Symbol of a token you wish to disable for sale via Hydro API
* @param wait If true, the promise will only resolve when the transaction is confirmed
* @return Transaction hash
*/
HydroClient.prototype.disableToken = function (symbol, wait) {
return __awaiter(this, void 0, void 0, function () {
var tokenAddress;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getTokenAddress(symbol)];
case 1:
tokenAddress = _a.sent();
return [2 /*return*/, this.web3Handler.disableToken(tokenAddress, wait)];
}
});
});
};
HydroClient.prototype.getTokenAddress = function (token) {
return __awaiter(this, void 0, void 0, function () {
var data, address;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!this.tokenAddresses.get(token)) return [3 /*break*/, 2];
return [4 /*yield*/, this.apiHandler.get(path_1.join('tokens', token))];
case 1:
data = _a.sent();
this.tokenAddresses.set(token, data.token.address);
_a.label = 2;
case 2:
address = this.tokenAddresses.get(token);
if (!address) {
throw new Error('Unable to get token address');
}
return [2 /*return*/, address];
}
});
});
};
return HydroClient;
}());
exports.HydroClient = HydroClient;
// This SDK only supports EthSign for the moment, so no need to export this.
var SignatureMethod;
(function (SignatureMethod) {
SignatureMethod[SignatureMethod["ETH_SIGN"] = 0] = "ETH_SIGN";
SignatureMethod[SignatureMethod["EIP_712"] = 1] = "EIP_712";
})(SignatureMethod || (SignatureMethod = {}));