polymarketClient.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import axios from "axios";
  2. import qs from "qs";
  3. import { HttpsProxyAgent } from "https-proxy-agent";
  4. import { ClobClient, Side, OrderType } from "@polymarket/clob-client";
  5. import { Wallet } from "ethers";
  6. import WebSocketClient from "./WebSocketClient.js";
  7. import Logs from "./logs.js";
  8. /**
  9. * axios 默认配置
  10. */
  11. const axiosDefaultOptions = {
  12. baseURL: "",
  13. url: "",
  14. method: "GET",
  15. headers: {},
  16. params: {},
  17. data: {},
  18. timeout: 10000,
  19. };
  20. /**
  21. * 通用请求
  22. * @param {*} options
  23. * @param {*} baseURL
  24. * @returns
  25. */
  26. const clientRequest = async (options, baseURL) => {
  27. const { url } = options;
  28. if (!url || !baseURL) {
  29. throw new Error("url and baseURL are required");
  30. }
  31. const mergedOptions = {
  32. ...axiosDefaultOptions,
  33. ...options,
  34. baseURL,
  35. paramsSerializer: params => qs.stringify(params, { arrayFormat: 'repeat' })
  36. };
  37. const proxy = process.env.NODE_HTTP_PROXY;
  38. if (proxy) {
  39. mergedOptions.proxy = false;
  40. mergedOptions.httpsAgent = new HttpsProxyAgent(proxy);
  41. }
  42. return axios(mergedOptions).then(res => res.data);
  43. }
  44. /**
  45. * 请求市场数据
  46. * @param {*} options
  47. * @returns
  48. */
  49. const requestMarketData = async (options) => {
  50. return clientRequest(options, "https://gamma-api.polymarket.com");
  51. }
  52. /**
  53. * 请求订单簿数据
  54. */
  55. const requestClobData = async (options) => {
  56. return clientRequest(options, "https://clob.polymarket.com");
  57. }
  58. /**
  59. * 获取足球联赛
  60. * @returns {Promise}
  61. */
  62. export const getSoccerSports = async () => {
  63. return requestMarketData({ url: "/sports" })
  64. .then(sportsData => {
  65. return sportsData.filter(item => {
  66. const { tags } = item;
  67. const tagIds = tags.split(",").map(item => +item);
  68. return tagIds.includes(100350);
  69. });
  70. });
  71. }
  72. /**
  73. * 获取赛事数据
  74. * @param {Object} options
  75. * @param {number} options.limit
  76. * @param {number} options.tag_id
  77. * @param {boolean} options.active
  78. * @param {boolean} options.closed
  79. * @param {string} options.endDateMin
  80. * @param {string} options.endDateMax
  81. * @returns {Promise}
  82. */
  83. export const getEvents = async ({
  84. limit = 500, tag_id = 100350, active = true,
  85. closed = false, endDateMin = "", endDateMax = "",
  86. } = {}) => {
  87. return requestMarketData({
  88. url: "/events",
  89. params: {
  90. limit, tag_id, active, closed,
  91. end_date_min: endDateMin,
  92. end_date_max: endDateMax,
  93. }
  94. }).then(events => events.filter(item => !!item.series));
  95. }
  96. /**
  97. * 获取订单簿数据
  98. */
  99. export const getOrderBook = async (tokenId) => {
  100. return requestClobData({
  101. url: "/book",
  102. params: {
  103. token_id: tokenId,
  104. }
  105. });
  106. }
  107. /**
  108. * 批量获取订单簿数据
  109. * @param {Array} tokenIds
  110. * @returns {Promise}
  111. */
  112. export const getMultipleOrderBooks = async (tokenIds) => {
  113. return requestClobData({
  114. url: "/books",
  115. method: 'POST',
  116. headers: {
  117. 'Content-Type': 'application/json',
  118. },
  119. data: tokenIds.map(tokenId => ({ token_id: tokenId })),
  120. });
  121. }
  122. /**
  123. * 下注
  124. */
  125. export const placeOrder = async (info) => {
  126. // return Promise.resolve(info);
  127. const { asset_id, bestPrice, stakeSize, tick_size: tickSize, neg_risk: negRisk } = info;
  128. const HOST = "https://clob.polymarket.com";
  129. const CHAIN_ID = 137; // Polygon mainnet
  130. const signer = new Wallet(process.env.POLYMARKET_PRIVATE_KEY);
  131. const userApiCreds = {
  132. key: process.env.POLYMARKET_API_KEY,
  133. secret: process.env.POLYMARKET_API_SECRET,
  134. passphrase: process.env.POLYMARKET_API_PASSPHRASE,
  135. };
  136. const SIGNATURE_TYPE = 0;
  137. const FUNDER_ADDRESS = signer.address;
  138. // Logs.outDev('clob client init', HOST, CHAIN_ID, signer.address, userApiCreds, SIGNATURE_TYPE, FUNDER_ADDRESS);
  139. const client = new ClobClient(
  140. HOST,
  141. CHAIN_ID,
  142. signer,
  143. userApiCreds,
  144. SIGNATURE_TYPE,
  145. FUNDER_ADDRESS
  146. );
  147. const orderData = {
  148. tokenID: asset_id,
  149. price: bestPrice,
  150. size: stakeSize,
  151. side: Side.BUY,
  152. }
  153. const orderOptions = { tickSize, negRisk }
  154. Logs.outDev('polymarket place order data', orderData, orderOptions, OrderType.FOK);
  155. // return client.getBalanceAllowance({ asset_type: 'COLLATERAL' })
  156. return client.createAndPostOrder(orderData, orderOptions, OrderType.FOK)
  157. .then(res => {
  158. // if (res.error) {
  159. // return Promise.reject(new Error(res.error));
  160. // }
  161. return res;
  162. });
  163. // return Promise.resolve(info);
  164. // return Promise.reject(new Error('polymarket place order not implemented', { cause: 400 }));
  165. }
  166. /**
  167. * 请求平台数据
  168. * @param {*} options
  169. * @returns {Promise}
  170. */
  171. export const platformRequest = async (options) => {
  172. const { url } = options;
  173. if (!url) {
  174. throw new Error("url is required");
  175. }
  176. const mergedOptions = {
  177. ...axiosDefaultOptions,
  178. ...options,
  179. baseURL: "http://127.0.0.1:9020",
  180. };
  181. return axios(mergedOptions).then(res => res.data);
  182. }
  183. /**
  184. * 请求平台 POST 数据
  185. * @param {string} url
  186. * @param {Object} data
  187. * @returns {Promise}
  188. */
  189. export const platformPost = async (url, data) => {
  190. return platformRequest({
  191. url,
  192. method: 'POST',
  193. headers: {
  194. 'Content-Type': 'application/json',
  195. },
  196. data,
  197. });
  198. }
  199. /**
  200. * 请求平台 GET 数据
  201. * @param {string} url
  202. * @param {Object} params
  203. * @returns {Promise}
  204. */
  205. export const platformGet = async (url, params) => {
  206. return platformRequest({ url, method: 'GET', params });
  207. }
  208. /**
  209. * 市场 WebSocket 客户端
  210. */
  211. export class MarketWsClient extends WebSocketClient {
  212. #assetIds = [];
  213. constructor() {
  214. let agent;
  215. const proxy = process.env.NODE_HTTP_PROXY;
  216. if (proxy) {
  217. agent = new HttpsProxyAgent(proxy);
  218. }
  219. super("wss://ws-subscriptions-clob.polymarket.com/ws/market", { agent });
  220. }
  221. connect() {
  222. super.connect();
  223. this.on('open', () => {
  224. if (this.#assetIds.length > 0) {
  225. this.subscribeToTokensIds(this.#assetIds);
  226. }
  227. });
  228. }
  229. subscribeToTokensIds(assetIds) {
  230. this.#assetIds = [...new Set([...this.#assetIds, ...assetIds])];
  231. this.send({
  232. operation: "subscribe",
  233. assets_ids: assetIds,
  234. });
  235. }
  236. unsubscribeToTokensIds(assetIds) {
  237. const assetIdsSet = new Set(assetIds);
  238. this.#assetIds = this.#assetIds.filter(id => !assetIdsSet.has(id));
  239. this.send({
  240. operation: "unsubscribe",
  241. assets_ids: assetIds,
  242. });
  243. }
  244. }