@sx-bet/sportx-js
Version:
Provides an easy to use API to interact with the SportX relayer.
612 lines • 29.8 kB
JavaScript
;
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