UNPKG

@sx-bet/sportx-js

Version:

Provides an easy to use API to interact with the SportX relayer.

612 lines 29.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getValidEnv = exports.newSportX = void 0; const bignumber_1 = require("@ethersproject/bignumber"); const bytes_1 = require("@ethersproject/bytes"); const constants_1 = require("@ethersproject/constants"); const contracts_1 = require("@ethersproject/contracts"); const providers_1 = require("@ethersproject/providers"); const random_1 = require("@ethersproject/random"); const wallet_1 = require("@ethersproject/wallet"); const ably = __importStar(require("ably")); const cross_fetch_1 = __importDefault(require("cross-fetch")); const debug_1 = __importDefault(require("debug")); const eth_sig_util_1 = __importDefault(require("eth-sig-util")); const query_string_1 = __importDefault(require("query-string")); const ChildERC20_json_1 = __importDefault(require("./artifacts/ChildERC20.json")); const ChildToken_json_1 = __importDefault(require("./artifacts/ChildToken.json")); const constants_2 = require("./constants"); const api_error_1 = require("./errors/api_error"); const schema_error_1 = require("./errors/schema_error"); const convert_1 = require("./utils/convert"); const misc_1 = require("./utils/misc"); const networks_1 = require("./utils/networks"); const signing_1 = require("./utils/signing"); const validation_1 = require("./utils/validation"); class SportX { constructor(env, customProviderUrl, privateKey, customProvider, apiUrl, apiKey) { this.initialized = false; this.debug = debug_1.default("sportx-js"); this.baseTokenWrappers = {}; if (!Object.values(constants_2.Environments).includes(env)) { throw new Error(`Invalid environment: ${env}`); } this.environment = env; this.network = networks_1.getNetwork(this.environment); let providerUrl; providerUrl = constants_2.DEFAULT_RPC_URLS[env]; if (customProviderUrl) { providerUrl = customProviderUrl; } if (privateKey && !bytes_1.isHexString(privateKey)) { throw new Error(`${privateKey} is not a valid private key.`); } else if (privateKey) { this.provider = new providers_1.StaticJsonRpcProvider(providerUrl, constants_2.CHAIN_IDS[this.network]); this.signingWallet = new wallet_1.Wallet(privateKey).connect(this.provider); this.privateKey = privateKey; } else if (customProvider) { this.signingWallet = customProvider.getSigner(0); this.provider = customProvider; } else { throw new Error(`Neither privateKey nor both providers provided.`); } this.apiKey = apiKey || ""; this.relayerUrl = apiUrl || constants_2.RELAYER_URLS[env]; } cancelOrder(orderHashes) { return __awaiter(this, void 0, void 0, function* () { this.debug("cancelOrder"); if (!Array.isArray(orderHashes)) { throw new schema_error_1.APISchemaError("orderHashes is not an array"); } if (!orderHashes.every((hash) => bytes_1.isHexString(hash))) { throw new schema_error_1.APISchemaError("orderHashes has some invalid order hashes."); } const salt = `0x${Buffer.from(random_1.randomBytes(32)).toString("hex")}`; const timestamp = Math.floor(new Date().getTime() / 1000); const cancelOrderPayload = signing_1.getCancelOrderEIP712Payload(orderHashes, salt, timestamp, this.sidechainChainId); this.debug("Signing payload"); this.debug(cancelOrderPayload); const signature = yield this.getEip712Signature(cancelOrderPayload); const payload = { signature, orderHashes, salt, maker: yield this.signingWallet.getAddress(), timestamp, }; this.debug("Cancel order payload"); this.debug(payload); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.CANCEL_ORDERS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't cancel orders."); this.debug("Relayer response"); this.debug(result); return result; }); } cancelAllOrders() { return __awaiter(this, void 0, void 0, function* () { this.debug("cancelAllOrders"); const salt = `0x${Buffer.from(random_1.randomBytes(32)).toString("hex")}`; const timestamp = Math.floor(new Date().getTime() / 1000); const cancelOrderPayload = signing_1.getCancelAllOrdersEIP712Payload(salt, timestamp, this.sidechainChainId); this.debug("Signing payload"); this.debug(cancelOrderPayload); const signature = yield this.getEip712Signature(cancelOrderPayload); const payload = { signature, salt, maker: yield this.signingWallet.getAddress(), timestamp, }; this.debug("Cancel all orders payload"); this.debug(payload); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.CANCEL_ALL_ORDERS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't cancel orders."); this.debug("Relayer response"); this.debug(result); return result; }); } cancelOrdersByEvent(sportXeventId) { return __awaiter(this, void 0, void 0, function* () { this.debug("cancelOrderByEvent"); if (typeof sportXeventId !== "string") { throw new schema_error_1.APISchemaError("sportXeventId is not a string"); } const salt = `0x${Buffer.from(random_1.randomBytes(32)).toString("hex")}`; const timestamp = Math.floor(new Date().getTime() / 1000); const cancelOrderPayload = signing_1.getCancelOrderEventsEIP712Payload(sportXeventId, salt, timestamp, this.sidechainChainId); this.debug("Signing payload"); this.debug(cancelOrderPayload); const signature = yield this.getEip712Signature(cancelOrderPayload); const payload = { signature, sportXeventId, salt, maker: yield this.signingWallet.getAddress(), timestamp, }; this.debug("Cancel order event payload"); this.debug(payload); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.CANCEL_EVENT_ORDERS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't cancel orders."); this.debug("Relayer response"); this.debug(result); return result; }); } getPopularMarkets() { return __awaiter(this, void 0, void 0, function* () { this.debug("getPopularMarkets"); const url = `${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.POPULAR}`; const response = yield cross_fetch_1.default(url); const result = yield this.tryParseResponse(response, "Can't fetch active markets"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getRealtimeConnection() { return this.ably; } init() { return __awaiter(this, void 0, void 0, function* () { if (this.initialized) { throw new Error("Already initialized"); } this.ably = new ably.Realtime.Promise({ authUrl: `${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.USER_TOKEN}`, }); yield new Promise((resolve, reject) => { this.ably.connection.on("connected", () => { resolve(); }); setTimeout(() => reject(), constants_2.RELAYER_TIMEOUT); }); this.metadata = yield this.getMetadata(); const sidechainNetwork = yield this.provider.getNetwork(); this.sidechainChainId = sidechainNetwork.chainId; this.verifyChainIds(); Object.entries(constants_2.TOKEN_ADDRESSES[this.network]).map(([, address]) => __awaiter(this, void 0, void 0, function* () { this.baseTokenWrappers[address] = new contracts_1.Contract(address, ChildERC20_json_1.default.abi, this.provider); })); this.initialized = true; this.debug("Initialized"); }); } getActiveLeagues() { return __awaiter(this, void 0, void 0, function* () { this.debug("getLeagues"); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.ACTIVE_LEAGUES}`); const result = yield this.tryParseResponse(response, "Can't fetch leagues"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getMetadata() { return __awaiter(this, void 0, void 0, function* () { this.debug("getMetadata"); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.METADATA}`); const result = yield this.tryParseResponse(response, "Can't fetch metadata"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getLeagues() { return __awaiter(this, void 0, void 0, function* () { this.debug("getLeagues"); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.LEAGUES}`); const result = yield this.tryParseResponse(response, "Can't fetch leagues"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getSports() { return __awaiter(this, void 0, void 0, function* () { this.debug("getSports"); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.SPORTS}`); const result = yield this.tryParseResponse(response, "Can't fetch sports"); const { data } = result; return data; }); } getLiveScores(eventIds) { return __awaiter(this, void 0, void 0, function* () { this.debug("getLiveScores"); if (!Array.isArray(eventIds) || !eventIds.every((eventId) => typeof eventId === "string")) { throw new schema_error_1.APISchemaError("eventIds is not an array of strings"); } const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.LIVE_SCORES}`, { method: "POST", body: JSON.stringify({ sportXEventIds: eventIds }), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't get live scores"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getActiveMarkets(mainLinesOnly, eventId, leagueId, liveOnly, betGroup) { return __awaiter(this, void 0, void 0, function* () { this.debug("getActiveMarkets"); const qs = query_string_1.default.stringify(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (mainLinesOnly !== undefined && { onlyMainLine: mainLinesOnly })), (leagueId !== undefined && { leagueId })), (eventId !== undefined && { eventId })), (liveOnly !== undefined && { liveOnly })), (betGroup !== undefined && { betGroup }))); const url = `${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.ACTIVE_MARKETS}?${qs}`; const response = yield cross_fetch_1.default(url); const result = yield this.tryParseResponse(response, "Can't fetch active markets"); this.debug("Relayer response"); this.debug(result); const { data: { markets }, } = result; return markets; }); } marketLookup(marketHashes) { return __awaiter(this, void 0, void 0, function* () { this.debug("marketLookup"); const payload = { marketHashes, }; if (!Array.isArray(marketHashes) || !marketHashes.every((hash) => bytes_1.isHexString(hash))) { throw new schema_error_1.APISchemaError("marketHashes is not a hex string "); } const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.HISTORICAL_MARKETS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't lookup markets"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } newOrder(orders) { return __awaiter(this, void 0, void 0, function* () { this.debug("newOrder"); const validation = orders.map(validation_1.validateINewOrderSchema); validation.forEach((val) => { if (val !== "OK") { throw new schema_error_1.APISchemaError(val); } }); const walletAddress = yield this.signingWallet.getAddress(); const apiOrders = yield Promise.all(orders.map((order) => __awaiter(this, void 0, void 0, function* () { const bigNumBetSize = bignumber_1.BigNumber.from(order.totalBetSize); const salt = bignumber_1.BigNumber.from(random_1.randomBytes(32)).toString(); const apiMakerOrder = { marketHash: order.marketHash, maker: walletAddress, totalBetSize: bigNumBetSize.toString(), percentageOdds: order.percentageOdds, expiry: 2209006800, apiExpiry: order.expiry, executor: this.metadata.executorAddress, baseToken: order.baseToken, salt, isMakerBettingOutcomeOne: order.isMakerBettingOutcomeOne, }; const signature = yield signing_1.getOrderSignature(apiMakerOrder, this.signingWallet); const signedApiMakerOrder = Object.assign(Object.assign({}, apiMakerOrder), { signature }); return signedApiMakerOrder; }))); this.debug(`New signed orders`); this.debug(apiOrders); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.NEW_ORDER}`, { method: "POST", body: JSON.stringify({ orders: apiOrders }), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't submit new order"); this.debug("Relayer response"); this.debug(result); return result; }); } fillOrders(orders, takerAmounts, fillDetailsMetadata, affiliateAddress, approveProxyPayload) { return __awaiter(this, void 0, void 0, function* () { this.debug("fillOrders"); orders.forEach((order) => { const validation = validation_1.validateISignedRelayerMakerOrder(order); if (validation !== "OK") { this.debug("One of the orders is malformed"); throw new schema_error_1.APISchemaError(validation); } }); if (affiliateAddress && !validation_1.isAddress(affiliateAddress)) { this.debug("Affiliate address is malformed"); throw new schema_error_1.APISchemaError("Affiliate address malformed."); } if (fillDetailsMetadata) { const validation = validation_1.validateIFillDetailsMetadata(fillDetailsMetadata); if (validation !== "OK") { this.debug("Metadata malformed"); throw new schema_error_1.APISchemaError(validation); } } if (!Array.isArray(takerAmounts)) { throw new schema_error_1.APISchemaError("takerAmounts is not an array"); } if (!takerAmounts.every((amount) => validation_1.isPositiveBigNumber(amount))) { throw new schema_error_1.APISchemaError("takerAmounts has some invalid number strings"); } const fillSalt = bignumber_1.BigNumber.from(random_1.randomBytes(32)); const solidityOrders = orders.map(convert_1.convertToContractOrder); const orderHashes = solidityOrders.map(signing_1.getOrderHash); const finalFillDetailsMetadata = fillDetailsMetadata || { action: "N/A", market: "N/A", betting: "N/A", stake: "N/A", odds: "N/A", returning: "N/A", }; const fillDetails = Object.assign(Object.assign({}, finalFillDetailsMetadata), { fills: { orders: orders.map(convert_1.convertToContractOrder), makerSigs: orders.map((order) => order.signature), takerAmounts: takerAmounts.map(bignumber_1.BigNumber.from), fillSalt, beneficiary: constants_1.AddressZero, } }); const fillOrderPayload = signing_1.getFillOrderEIP712Payload(fillDetails, this.sidechainChainId, constants_2.EIP712_VERSION[this.environment], constants_2.EIP712_FILL_HASHER_ADDRESSES[this.environment]); this.debug(`EIP712 payload`); this.debug(fillOrderPayload); const takerSignature = yield this.getEip712Signature(fillOrderPayload); const payload = Object.assign(Object.assign({ orderHashes, takerAmounts, taker: yield this.signingWallet.getAddress(), takerSig: takerSignature, fillSalt: fillSalt.toString() }, finalFillDetailsMetadata), { affiliateAddress, approveProxyPayload }); if (approveProxyPayload) { this.debug(`approveProxyPayload`); this.debug(approveProxyPayload); } this.debug("Meta fill payload"); this.debug(payload); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.FILL_ORDERS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't fill orders."); this.debug("Relayer response"); this.debug(result); return result; }); } getPendingOrFailedBets(request) { return __awaiter(this, void 0, void 0, function* () { this.debug("getRecentPendingBets"); const validation = validation_1.validateIGetPendingBetsRequest(request); if (validation !== "OK") { throw new schema_error_1.APISchemaError(validation); } const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.PENDING_BETS}`, { method: "POST", body: JSON.stringify(request), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't get recent pending bets"); this.debug("Relayer response"); this.debug(result); const { data: { bets }, } = result; const pendingBets = bets; return pendingBets; }); } getTrades(tradeRequest) { return __awaiter(this, void 0, void 0, function* () { this.debug("getTrades"); const validation = validation_1.validateIGetTradesRequest(tradeRequest); if (validation !== "OK") { throw new schema_error_1.APISchemaError(validation); } const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.TRADES}`, { method: "POST", body: JSON.stringify(tradeRequest), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't get trades"); this.debug("Relayer response"); this.debug(result); const { data } = result; return data; }); } getOrders(marketHashes, maker, baseToken) { return __awaiter(this, void 0, void 0, function* () { this.debug("getOrders"); if (marketHashes && !marketHashes.every((hash) => bytes_1.isHexString(hash))) { throw new schema_error_1.APISchemaError(`One of the supplied market hashes is not a valid hex string.`); } if (maker && !validation_1.isAddress(maker)) { throw new schema_error_1.APISchemaError(`maker is not a valid address`); } if (baseToken && !validation_1.isAddress(baseToken)) { throw new schema_error_1.APISchemaError(`baseToken is not a valid address`); } const payload = Object.assign(Object.assign(Object.assign({}, (marketHashes && { marketHashes })), (maker && { maker })), (baseToken && { baseToken })); const response = yield cross_fetch_1.default(`${this.relayerUrl}${constants_2.RELAYER_HTTP_ENDPOINTS.ORDERS}`, { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, }); const result = yield this.tryParseResponse(response, "Can't get orders"); this.debug("Relayer response"); this.debug(result); const { data } = result; const orders = data; return orders; }); } approveSportXContracts(token) { return __awaiter(this, void 0, void 0, function* () { const tokenContract = new contracts_1.Contract(token, ChildToken_json_1.default.abi, this.signingWallet); const approvalTxn = yield tokenContract.approve(constants_2.TOKEN_TRANSFER_PROXY_ADDRESS[this.environment], constants_1.MaxUint256); const receipt = yield approvalTxn.wait(); this.debug("Approval Txn reciept"); this.debug(receipt); return receipt; }); } getEip712Signature(payload) { return __awaiter(this, void 0, void 0, function* () { if (this.privateKey) { const bufferPrivateKey = Buffer.from(this.privateKey.substring(2), "hex"); const signature = eth_sig_util_1.default.signTypedData_v4(bufferPrivateKey, { data: payload }); return signature; } else if (this.provider._web3Provider.isMetaMask === true) { const walletAddress = yield this.signingWallet.getAddress(); const signature = yield this.provider.send("eth_signTypedData_v4", [walletAddress, JSON.stringify(payload)]); return signature; } else { const walletAddress = yield this.signingWallet.getAddress(); const signature = yield this.provider.send("eth_signTypedData", [ walletAddress, payload, ]); return signature; } }); } tryParseResponse(response, errorMessage) { return __awaiter(this, void 0, void 0, function* () { const textResponse = yield response.text(); const { result, valid } = misc_1.tryParseJson(textResponse); if (valid && response.status !== 200) { this.debug(response.status); this.debug(response.statusText); this.debug(result); throw new api_error_1.APIError(result, `${errorMessage}. Response code: ${response.status}`); } else if (!valid) { throw new api_error_1.APIError(undefined, `Can't parse JSON ${textResponse}`); } else { return result; } }); } verifyChainIds() { if (this.environment === constants_2.Environments.SxMainnet) { if (this.sidechainChainId !== constants_2.CHAIN_IDS[constants_2.Networks.SX_MAINNET]) { throw new Error(`Incorrect sidechain chain ID for production environment. Are you sure the passed sidechain provider is pointing to Polygon mainnet?`); } } else if (this.environment === constants_2.Environments.SxToronto) { if (this.sidechainChainId !== constants_2.CHAIN_IDS[constants_2.Networks.SX_TORONTO]) { throw new Error(`Incorrect sidechain chain ID for sx_toronto environment. Are you sure the passed sidechain provider is pointing to Toronto?`); } } } } function newSportX(sportXObj) { return __awaiter(this, void 0, void 0, function* () { const { env, customSidechainProviderUrl, privateKey, sidechainProvider, apiUrl, apiKey, } = sportXObj; const sportX = new SportX(env, customSidechainProviderUrl, privateKey, sidechainProvider, apiUrl, apiKey); yield sportX.init(); return sportX; }); } exports.newSportX = newSportX; function getValidEnv(env) { switch (env) { case constants_2.Environments.SxMainnet: return constants_2.Environments.SxMainnet; case constants_2.Environments.SxStage: return constants_2.Environments.SxStage; case constants_2.Environments.SxToronto: return constants_2.Environments.SxToronto; default: throw new Error(`Unknown environment ${env}`); } } exports.getValidEnv = getValidEnv; //# sourceMappingURL=sportx.js.map