ccxt 4.5.82 → 4.5.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/README.md +5 -4
  2. package/dist/ccxt.browser.min.js +2 -2
  3. package/dist/cjs/ccxt.js +3 -1
  4. package/dist/cjs/src/abstract/prediction/sxbet.js +11 -0
  5. package/dist/cjs/src/apex.js +1 -0
  6. package/dist/cjs/src/aster.js +2 -2
  7. package/dist/cjs/src/base/Exchange.js +1 -1
  8. package/dist/cjs/src/bigone.js +2 -2
  9. package/dist/cjs/src/binance.js +10 -10
  10. package/dist/cjs/src/bingx.js +3 -3
  11. package/dist/cjs/src/bit2c.js +4 -4
  12. package/dist/cjs/src/bitbank.js +15 -15
  13. package/dist/cjs/src/bitfinex.js +15 -15
  14. package/dist/cjs/src/bitget.js +109 -73
  15. package/dist/cjs/src/bitmex.js +7 -7
  16. package/dist/cjs/src/bitopro.js +2 -2
  17. package/dist/cjs/src/bitrue.js +6 -6
  18. package/dist/cjs/src/bitso.js +18 -18
  19. package/dist/cjs/src/bitstamp.js +5 -5
  20. package/dist/cjs/src/bitteam.js +32 -32
  21. package/dist/cjs/src/bittrade.js +10 -10
  22. package/dist/cjs/src/bitvavo.js +9 -9
  23. package/dist/cjs/src/blockchaincom.js +2 -2
  24. package/dist/cjs/src/btcbox.js +2 -2
  25. package/dist/cjs/src/btcmarkets.js +2 -2
  26. package/dist/cjs/src/btcturk.js +2 -2
  27. package/dist/cjs/src/btse.js +2 -2
  28. package/dist/cjs/src/bybitid.js +4 -0
  29. package/dist/cjs/src/coinbase.js +1 -1
  30. package/dist/cjs/src/coinbaseexchange.js +13 -13
  31. package/dist/cjs/src/coincheck.js +4 -4
  32. package/dist/cjs/src/coinex.js +10 -10
  33. package/dist/cjs/src/coinmate.js +4 -4
  34. package/dist/cjs/src/coinone.js +4 -4
  35. package/dist/cjs/src/coinsph.js +5 -5
  36. package/dist/cjs/src/cryptocom.js +5 -5
  37. package/dist/cjs/src/deribit.js +31 -31
  38. package/dist/cjs/src/derive.js +1 -1
  39. package/dist/cjs/src/digifinex.js +31 -31
  40. package/dist/cjs/src/gate.js +25 -25
  41. package/dist/cjs/src/gemini.js +5 -5
  42. package/dist/cjs/src/grvt.js +1 -1
  43. package/dist/cjs/src/hibachi.js +5 -5
  44. package/dist/cjs/src/hitbtc.js +15 -15
  45. package/dist/cjs/src/hollaex.js +15 -15
  46. package/dist/cjs/src/htx.js +52 -52
  47. package/dist/cjs/src/hyperliquid.js +2 -2
  48. package/dist/cjs/src/independentreserve.js +2 -2
  49. package/dist/cjs/src/indodax.js +8 -8
  50. package/dist/cjs/src/kraken.js +29 -29
  51. package/dist/cjs/src/krakenfutures.js +16 -16
  52. package/dist/cjs/src/kucoin.js +15 -15
  53. package/dist/cjs/src/latoken.js +8 -8
  54. package/dist/cjs/src/lbank.js +31 -31
  55. package/dist/cjs/src/lighter.js +2 -2
  56. package/dist/cjs/src/luno.js +2 -2
  57. package/dist/cjs/src/mercado.js +12 -12
  58. package/dist/cjs/src/mexc.js +27 -27
  59. package/dist/cjs/src/modetrade.js +10 -10
  60. package/dist/cjs/src/ndax.js +4 -4
  61. package/dist/cjs/src/okx.js +19 -20
  62. package/dist/cjs/src/onetrading.js +7 -7
  63. package/dist/cjs/src/p2b.js +8 -8
  64. package/dist/cjs/src/pacifica.js +9 -3
  65. package/dist/cjs/src/paymium.js +2 -2
  66. package/dist/cjs/src/phemex.js +37 -37
  67. package/dist/cjs/src/poloniex.js +14 -14
  68. package/dist/cjs/src/prediction/sxbet.js +2710 -0
  69. package/dist/cjs/src/pro/alpaca.js +5 -5
  70. package/dist/cjs/src/pro/apex.js +6 -6
  71. package/dist/cjs/src/pro/aster.js +8 -8
  72. package/dist/cjs/src/pro/backpack.js +1 -1
  73. package/dist/cjs/src/pro/binance.js +26 -26
  74. package/dist/cjs/src/pro/bingx.js +11 -11
  75. package/dist/cjs/src/pro/bitfinex.js +11 -11
  76. package/dist/cjs/src/pro/bitget.js +20 -20
  77. package/dist/cjs/src/pro/bithumb.js +1 -1
  78. package/dist/cjs/src/pro/bitmex.js +12 -12
  79. package/dist/cjs/src/pro/bitopro.js +5 -5
  80. package/dist/cjs/src/pro/bitrue.js +7 -7
  81. package/dist/cjs/src/pro/bitstamp.js +5 -5
  82. package/dist/cjs/src/pro/bittrade.js +11 -11
  83. package/dist/cjs/src/pro/bitvavo.js +16 -16
  84. package/dist/cjs/src/pro/blockchaincom.js +5 -5
  85. package/dist/cjs/src/pro/blofin.js +2 -2
  86. package/dist/cjs/src/pro/bybit.js +21 -21
  87. package/dist/cjs/src/pro/bydfi.js +2 -2
  88. package/dist/cjs/src/pro/cex.js +19 -19
  89. package/dist/cjs/src/pro/coinbase.js +2 -2
  90. package/dist/cjs/src/pro/coinbaseexchange.js +9 -9
  91. package/dist/cjs/src/pro/coinbaseinternational.js +2 -2
  92. package/dist/cjs/src/pro/coincheck.js +2 -2
  93. package/dist/cjs/src/pro/coinex.js +3 -3
  94. package/dist/cjs/src/pro/coinone.js +6 -6
  95. package/dist/cjs/src/pro/cryptocom.js +10 -10
  96. package/dist/cjs/src/pro/deribit.js +13 -13
  97. package/dist/cjs/src/pro/derive.js +4 -4
  98. package/dist/cjs/src/pro/dydx.js +1 -1
  99. package/dist/cjs/src/pro/extended.js +16 -10
  100. package/dist/cjs/src/pro/gate.js +10 -10
  101. package/dist/cjs/src/pro/gemini.js +3 -3
  102. package/dist/cjs/src/pro/grvt.js +2 -2
  103. package/dist/cjs/src/pro/hashkey.js +2 -2
  104. package/dist/cjs/src/pro/hitbtc.js +6 -6
  105. package/dist/cjs/src/pro/hollaex.js +3 -3
  106. package/dist/cjs/src/pro/htx.js +28 -28
  107. package/dist/cjs/src/pro/hyperliquid.js +1 -1
  108. package/dist/cjs/src/pro/independentreserve.js +2 -2
  109. package/dist/cjs/src/pro/kraken.js +7 -7
  110. package/dist/cjs/src/pro/krakenfutures.js +12 -12
  111. package/dist/cjs/src/pro/kucoin.js +12 -12
  112. package/dist/cjs/src/pro/lbank.js +16 -16
  113. package/dist/cjs/src/pro/luno.js +2 -2
  114. package/dist/cjs/src/pro/mexc.js +8 -8
  115. package/dist/cjs/src/pro/modetrade.js +3 -3
  116. package/dist/cjs/src/pro/mudrex.js +2 -2
  117. package/dist/cjs/src/pro/nado.js +3 -3
  118. package/dist/cjs/src/pro/ndax.js +6 -6
  119. package/dist/cjs/src/pro/okx.js +20 -20
  120. package/dist/cjs/src/pro/onetrading.js +14 -14
  121. package/dist/cjs/src/pro/p2b.js +5 -5
  122. package/dist/cjs/src/pro/pacifica.js +1 -1
  123. package/dist/cjs/src/pro/paradex.js +1 -1
  124. package/dist/cjs/src/pro/phemex.js +13 -13
  125. package/dist/cjs/src/pro/poloniex.js +17 -17
  126. package/dist/cjs/src/pro/toobit.js +2 -2
  127. package/dist/cjs/src/pro/upbit.js +4 -4
  128. package/dist/cjs/src/pro/weex.js +2 -2
  129. package/dist/cjs/src/pro/whitebit.js +12 -12
  130. package/dist/cjs/src/pro/woo.js +13 -13
  131. package/dist/cjs/src/pro/woofipro.js +3 -3
  132. package/dist/cjs/src/pro/xt.js +3 -3
  133. package/dist/cjs/src/tokocrypto.js +20 -20
  134. package/dist/cjs/src/upbit.js +12 -12
  135. package/dist/cjs/src/whitebit.js +15 -15
  136. package/dist/cjs/src/woo.js +3 -3
  137. package/dist/cjs/src/woofipro.js +14 -14
  138. package/dist/cjs/src/zaif.js +9 -3
  139. package/dist/cjs/src/zebpay.js +1 -1
  140. package/js/ccxt.d.ts +4 -1
  141. package/js/ccxt.js +3 -1
  142. package/js/src/abstract/prediction/sxbet.d.ts +41 -0
  143. package/js/src/abstract/prediction/sxbet.js +11 -0
  144. package/js/src/alpaca.d.ts +28 -33
  145. package/js/src/apex.d.ts +28 -42
  146. package/js/src/apex.js +1 -0
  147. package/js/src/aster.d.ts +58 -108
  148. package/js/src/aster.js +2 -2
  149. package/js/src/backpack.d.ts +34 -48
  150. package/js/src/base/Exchange.d.ts +304 -320
  151. package/js/src/base/Exchange.js +1 -1
  152. package/js/src/bigone.d.ts +24 -29
  153. package/js/src/bigone.js +2 -2
  154. package/js/src/binance.d.ts +112 -138
  155. package/js/src/binance.js +10 -10
  156. package/js/src/binancecoinm.d.ts +3 -2
  157. package/js/src/binanceusdm.d.ts +3 -2
  158. package/js/src/bingx.d.ts +65 -84
  159. package/js/src/bingx.js +3 -3
  160. package/js/src/bit2c.d.ts +13 -18
  161. package/js/src/bit2c.js +4 -4
  162. package/js/src/bitbank.d.ts +16 -21
  163. package/js/src/bitbank.js +15 -15
  164. package/js/src/bitbns.d.ts +15 -20
  165. package/js/src/bitfinex.d.ts +46 -51
  166. package/js/src/bitfinex.js +15 -15
  167. package/js/src/bitflyer.d.ts +20 -25
  168. package/js/src/bitget.d.ts +105 -93
  169. package/js/src/bitget.js +109 -73
  170. package/js/src/bithumb.d.ts +26 -31
  171. package/js/src/bitmex.d.ts +48 -65
  172. package/js/src/bitmex.js +7 -7
  173. package/js/src/bitopro.d.ts +25 -41
  174. package/js/src/bitopro.js +2 -2
  175. package/js/src/bitrue.d.ts +30 -45
  176. package/js/src/bitrue.js +6 -6
  177. package/js/src/bitso.d.ts +24 -29
  178. package/js/src/bitso.js +18 -18
  179. package/js/src/bitstamp.d.ts +36 -47
  180. package/js/src/bitstamp.js +5 -5
  181. package/js/src/bitteam.d.ts +22 -27
  182. package/js/src/bitteam.js +32 -32
  183. package/js/src/bittrade.d.ts +35 -46
  184. package/js/src/bittrade.js +10 -10
  185. package/js/src/bitvavo.d.ts +43 -48
  186. package/js/src/bitvavo.js +9 -9
  187. package/js/src/blockchaincom.d.ts +26 -31
  188. package/js/src/blockchaincom.js +2 -2
  189. package/js/src/blofin.d.ts +38 -43
  190. package/js/src/btcbox.d.ts +14 -19
  191. package/js/src/btcbox.js +2 -2
  192. package/js/src/btcmarkets.d.ts +25 -30
  193. package/js/src/btcmarkets.js +2 -2
  194. package/js/src/btcturk.d.ts +13 -18
  195. package/js/src/btcturk.js +2 -2
  196. package/js/src/btse.d.ts +44 -50
  197. package/js/src/btse.js +2 -2
  198. package/js/src/bullish.d.ts +35 -57
  199. package/js/src/bybit.d.ts +95 -106
  200. package/js/src/bybitid.js +4 -0
  201. package/js/src/bydfi.d.ts +37 -48
  202. package/js/src/cex.d.ts +29 -34
  203. package/js/src/coinbase.d.ts +66 -82
  204. package/js/src/coinbase.js +1 -1
  205. package/js/src/coinbaseexchange.d.ts +33 -43
  206. package/js/src/coinbaseexchange.js +13 -13
  207. package/js/src/coinbaseinternational.d.ts +34 -73
  208. package/js/src/coincheck.d.ts +13 -18
  209. package/js/src/coincheck.js +4 -4
  210. package/js/src/coinex.d.ts +63 -68
  211. package/js/src/coinex.js +10 -10
  212. package/js/src/coinmate.d.ts +15 -20
  213. package/js/src/coinmate.js +4 -4
  214. package/js/src/coinone.d.ts +14 -19
  215. package/js/src/coinone.js +4 -4
  216. package/js/src/coinsph.d.ts +32 -37
  217. package/js/src/coinsph.js +5 -5
  218. package/js/src/coinspot.d.ts +10 -15
  219. package/js/src/cryptocom.d.ts +48 -64
  220. package/js/src/cryptocom.js +5 -5
  221. package/js/src/cryptomus.d.ts +15 -23
  222. package/js/src/deepcoin.d.ts +43 -48
  223. package/js/src/delta.d.ts +43 -54
  224. package/js/src/deribit.d.ts +48 -69
  225. package/js/src/deribit.js +31 -31
  226. package/js/src/derive.d.ts +28 -42
  227. package/js/src/derive.js +1 -1
  228. package/js/src/digifinex.d.ts +54 -67
  229. package/js/src/digifinex.js +31 -31
  230. package/js/src/dydx.d.ts +26 -31
  231. package/js/src/extended.d.ts +51 -56
  232. package/js/src/foxbit.d.ts +34 -64
  233. package/js/src/gate.d.ts +94 -162
  234. package/js/src/gate.js +25 -25
  235. package/js/src/gemini.d.ts +33 -43
  236. package/js/src/gemini.js +5 -5
  237. package/js/src/grvt.d.ts +36 -47
  238. package/js/src/grvt.js +1 -1
  239. package/js/src/hashkey.d.ts +58 -78
  240. package/js/src/hibachi.d.ts +42 -52
  241. package/js/src/hibachi.js +5 -5
  242. package/js/src/hitbtc.d.ts +49 -56
  243. package/js/src/hitbtc.js +15 -15
  244. package/js/src/hollaex.d.ts +26 -31
  245. package/js/src/hollaex.js +15 -15
  246. package/js/src/htx.d.ts +80 -121
  247. package/js/src/htx.js +52 -52
  248. package/js/src/hyperliquid.d.ts +77 -127
  249. package/js/src/hyperliquid.js +2 -2
  250. package/js/src/independentreserve.d.ts +15 -20
  251. package/js/src/independentreserve.js +2 -2
  252. package/js/src/indodax.d.ts +19 -24
  253. package/js/src/indodax.js +8 -8
  254. package/js/src/kraken.d.ts +43 -55
  255. package/js/src/kraken.js +29 -29
  256. package/js/src/krakenfutures.d.ts +34 -39
  257. package/js/src/krakenfutures.js +16 -16
  258. package/js/src/kucoin.d.ts +121 -132
  259. package/js/src/kucoin.js +15 -15
  260. package/js/src/kucoinfutures.d.ts +4 -4
  261. package/js/src/latoken.d.ts +24 -29
  262. package/js/src/latoken.js +8 -8
  263. package/js/src/lbank.d.ts +40 -53
  264. package/js/src/lbank.js +31 -31
  265. package/js/src/lighter.d.ts +40 -45
  266. package/js/src/lighter.js +2 -2
  267. package/js/src/luno.d.ts +25 -30
  268. package/js/src/luno.js +2 -2
  269. package/js/src/mercado.d.ts +15 -20
  270. package/js/src/mercado.js +12 -12
  271. package/js/src/mexc.d.ts +70 -83
  272. package/js/src/mexc.js +27 -27
  273. package/js/src/modetrade.d.ts +43 -57
  274. package/js/src/modetrade.js +10 -10
  275. package/js/src/mudrex.d.ts +25 -35
  276. package/js/src/nado.d.ts +45 -50
  277. package/js/src/ndax.d.ts +27 -32
  278. package/js/src/ndax.js +4 -4
  279. package/js/src/okx.d.ts +97 -108
  280. package/js/src/okx.js +19 -20
  281. package/js/src/onetrading.d.ts +23 -31
  282. package/js/src/onetrading.js +7 -7
  283. package/js/src/p2b.d.ts +17 -22
  284. package/js/src/p2b.js +8 -8
  285. package/js/src/pacifica.d.ts +55 -73
  286. package/js/src/pacifica.js +9 -3
  287. package/js/src/paradex.d.ts +47 -68
  288. package/js/src/paymium.d.ts +12 -17
  289. package/js/src/paymium.js +2 -2
  290. package/js/src/phemex.d.ts +47 -52
  291. package/js/src/phemex.js +37 -37
  292. package/js/src/poloniex.d.ts +45 -50
  293. package/js/src/poloniex.js +14 -14
  294. package/js/src/prediction/sxbet.d.ts +595 -0
  295. package/js/src/prediction/sxbet.js +2708 -0
  296. package/js/src/pro/alpaca.d.ts +23 -23
  297. package/js/src/pro/alpaca.js +5 -5
  298. package/js/src/pro/apex.d.ts +28 -28
  299. package/js/src/pro/apex.js +6 -6
  300. package/js/src/pro/aster.d.ts +33 -33
  301. package/js/src/pro/aster.js +8 -8
  302. package/js/src/pro/backpack.d.ts +28 -28
  303. package/js/src/pro/backpack.js +1 -1
  304. package/js/src/pro/binance.d.ts +57 -57
  305. package/js/src/pro/binance.js +26 -26
  306. package/js/src/pro/bingx.d.ts +26 -26
  307. package/js/src/pro/bingx.js +11 -11
  308. package/js/src/pro/bitfinex.d.ts +25 -25
  309. package/js/src/pro/bitfinex.js +11 -11
  310. package/js/src/pro/bitget.d.ts +41 -41
  311. package/js/src/pro/bitget.js +20 -20
  312. package/js/src/pro/bithumb.d.ts +15 -15
  313. package/js/src/pro/bithumb.js +1 -1
  314. package/js/src/pro/bitmex.d.ts +29 -29
  315. package/js/src/pro/bitmex.js +12 -12
  316. package/js/src/pro/bitopro.d.ts +13 -13
  317. package/js/src/pro/bitopro.js +5 -5
  318. package/js/src/pro/bitrue.d.ts +25 -25
  319. package/js/src/pro/bitrue.js +7 -7
  320. package/js/src/pro/bitstamp.d.ts +21 -21
  321. package/js/src/pro/bitstamp.js +5 -5
  322. package/js/src/pro/bittrade.d.ts +19 -19
  323. package/js/src/pro/bittrade.js +11 -11
  324. package/js/src/pro/bitvavo.d.ts +62 -62
  325. package/js/src/pro/bitvavo.js +16 -16
  326. package/js/src/pro/blockchaincom.d.ts +19 -19
  327. package/js/src/pro/blockchaincom.js +5 -5
  328. package/js/src/pro/blofin.d.ts +23 -23
  329. package/js/src/pro/blofin.js +2 -2
  330. package/js/src/pro/bullish.d.ts +19 -25
  331. package/js/src/pro/bybit.d.ts +42 -45
  332. package/js/src/pro/bybit.js +21 -21
  333. package/js/src/pro/bydfi.d.ts +24 -24
  334. package/js/src/pro/bydfi.js +2 -2
  335. package/js/src/pro/cex.d.ts +37 -37
  336. package/js/src/pro/cex.js +19 -19
  337. package/js/src/pro/coinbase.d.ts +19 -19
  338. package/js/src/pro/coinbase.js +2 -2
  339. package/js/src/pro/coinbaseexchange.d.ts +25 -25
  340. package/js/src/pro/coinbaseexchange.js +9 -9
  341. package/js/src/pro/coinbaseinternational.d.ts +20 -20
  342. package/js/src/pro/coinbaseinternational.js +2 -2
  343. package/js/src/pro/coincheck.d.ts +5 -5
  344. package/js/src/pro/coincheck.js +2 -2
  345. package/js/src/pro/coinex.d.ts +24 -24
  346. package/js/src/pro/coinex.js +3 -3
  347. package/js/src/pro/coinone.d.ts +9 -11
  348. package/js/src/pro/coinone.js +6 -6
  349. package/js/src/pro/cryptocom.d.ts +40 -40
  350. package/js/src/pro/cryptocom.js +10 -10
  351. package/js/src/pro/deepcoin.d.ts +24 -32
  352. package/js/src/pro/deribit.d.ts +24 -24
  353. package/js/src/pro/deribit.js +13 -13
  354. package/js/src/pro/derive.d.ts +22 -22
  355. package/js/src/pro/derive.js +4 -4
  356. package/js/src/pro/dydx.d.ts +9 -9
  357. package/js/src/pro/dydx.js +1 -1
  358. package/js/src/pro/extended.d.ts +22 -22
  359. package/js/src/pro/extended.js +16 -10
  360. package/js/src/pro/gate.d.ts +50 -50
  361. package/js/src/pro/gate.js +10 -10
  362. package/js/src/pro/gemini.d.ts +26 -26
  363. package/js/src/pro/gemini.js +3 -3
  364. package/js/src/pro/grvt.d.ts +22 -22
  365. package/js/src/pro/grvt.js +2 -2
  366. package/js/src/pro/hashkey.d.ts +19 -19
  367. package/js/src/pro/hashkey.js +2 -2
  368. package/js/src/pro/hitbtc.d.ts +32 -32
  369. package/js/src/pro/hitbtc.js +6 -6
  370. package/js/src/pro/hollaex.d.ts +15 -17
  371. package/js/src/pro/hollaex.js +3 -3
  372. package/js/src/pro/htx.d.ts +36 -36
  373. package/js/src/pro/htx.js +28 -28
  374. package/js/src/pro/hyperliquid.d.ts +29 -31
  375. package/js/src/pro/hyperliquid.js +1 -1
  376. package/js/src/pro/independentreserve.d.ts +10 -10
  377. package/js/src/pro/independentreserve.js +2 -2
  378. package/js/src/pro/kraken.d.ts +41 -55
  379. package/js/src/pro/kraken.js +7 -7
  380. package/js/src/pro/krakenfutures.d.ts +32 -32
  381. package/js/src/pro/krakenfutures.js +12 -12
  382. package/js/src/pro/kucoin.d.ts +69 -72
  383. package/js/src/pro/kucoin.js +12 -12
  384. package/js/src/pro/kucoinfutures.d.ts +4 -4
  385. package/js/src/pro/lbank.d.ts +22 -22
  386. package/js/src/pro/lbank.js +16 -16
  387. package/js/src/pro/lighter.d.ts +27 -27
  388. package/js/src/pro/luno.d.ts +8 -8
  389. package/js/src/pro/luno.js +2 -2
  390. package/js/src/pro/mexc.d.ts +33 -33
  391. package/js/src/pro/mexc.js +8 -8
  392. package/js/src/pro/modetrade.d.ts +37 -39
  393. package/js/src/pro/modetrade.js +3 -3
  394. package/js/src/pro/mudrex.d.ts +8 -8
  395. package/js/src/pro/mudrex.js +2 -2
  396. package/js/src/pro/nado.d.ts +42 -46
  397. package/js/src/pro/nado.js +3 -3
  398. package/js/src/pro/ndax.d.ts +12 -12
  399. package/js/src/pro/ndax.js +6 -6
  400. package/js/src/pro/okx.d.ts +49 -49
  401. package/js/src/pro/okx.js +20 -20
  402. package/js/src/pro/onetrading.d.ts +28 -28
  403. package/js/src/pro/onetrading.js +14 -14
  404. package/js/src/pro/p2b.d.ts +15 -19
  405. package/js/src/pro/p2b.js +5 -5
  406. package/js/src/pro/pacifica.d.ts +22 -22
  407. package/js/src/pro/pacifica.js +1 -1
  408. package/js/src/pro/paradex.d.ts +18 -18
  409. package/js/src/pro/paradex.js +1 -1
  410. package/js/src/pro/phemex.d.ts +25 -25
  411. package/js/src/pro/phemex.js +13 -13
  412. package/js/src/pro/poloniex.d.ts +30 -32
  413. package/js/src/pro/poloniex.js +17 -17
  414. package/js/src/pro/toobit.d.ts +31 -31
  415. package/js/src/pro/toobit.js +2 -2
  416. package/js/src/pro/upbit.d.ts +24 -24
  417. package/js/src/pro/upbit.js +4 -4
  418. package/js/src/pro/weex.d.ts +39 -39
  419. package/js/src/pro/weex.js +2 -2
  420. package/js/src/pro/whitebit.d.ts +26 -30
  421. package/js/src/pro/whitebit.js +12 -12
  422. package/js/src/pro/woo.d.ts +44 -46
  423. package/js/src/pro/woo.js +13 -13
  424. package/js/src/pro/woofipro.d.ts +39 -41
  425. package/js/src/pro/woofipro.js +3 -3
  426. package/js/src/pro/xt.d.ts +20 -20
  427. package/js/src/pro/xt.js +3 -3
  428. package/js/src/revolutx.d.ts +17 -17
  429. package/js/src/tokocrypto.d.ts +25 -30
  430. package/js/src/tokocrypto.js +20 -20
  431. package/js/src/toobit.d.ts +45 -79
  432. package/js/src/upbit.d.ts +28 -61
  433. package/js/src/upbit.js +12 -12
  434. package/js/src/weex.d.ts +57 -68
  435. package/js/src/whitebit.d.ts +53 -64
  436. package/js/src/whitebit.js +15 -15
  437. package/js/src/woo.d.ts +61 -75
  438. package/js/src/woo.js +3 -3
  439. package/js/src/woofipro.d.ts +53 -67
  440. package/js/src/woofipro.js +14 -14
  441. package/js/src/xt.d.ts +57 -67
  442. package/js/src/zaif.d.ts +11 -16
  443. package/js/src/zaif.js +9 -3
  444. package/js/src/zebpay.d.ts +32 -37
  445. package/js/src/zebpay.js +1 -1
  446. package/package.json +2 -2
@@ -0,0 +1,2708 @@
1
+ // ----------------------------------------------------------------------------
2
+
3
+ // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+ // EDIT THE CORRESPONDENT .ts FILE INSTEAD
6
+
7
+ import { keccak_256 as keccak } from '@noble/hashes/sha3.js';
8
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
9
+ import Exchange from '../abstract/prediction/sxbet.js';
10
+ import { ecdsa } from '../base/functions/crypto.js';
11
+ import { ROUND, DECIMAL_PLACES, TICK_SIZE } from '../base/functions/number.js';
12
+ import { Precise } from '../base/Precise.js';
13
+ import { ArrayCache, ArrayCacheByOutcomeById } from '../base/ws/Cache.js';
14
+ import { AccountNotEnabled, ArgumentsRequired, AuthenticationError, BadRequest, BadSymbol, DuplicateOrderId, ExchangeError, InsufficientFunds, InvalidOrder, MarketClosed, NotSupported, OrderNotFillable, OrderNotFound, PermissionDenied } from '../base/errors.js';
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * @class sxbet
18
+ * @augments Exchange
19
+ */
20
+ export default class sxbet extends Exchange {
21
+ describe() {
22
+ return this.deepExtend(super.describe(), {
23
+ 'id': 'sxbet',
24
+ 'name': 'SX Bet',
25
+ 'countries': [],
26
+ 'rateLimit': 120, // 500 req/min baseline; /orders is 5500/min, /trades is 200/min
27
+ 'certified': false,
28
+ 'pro': true,
29
+ 'has': {
30
+ 'CORS': undefined,
31
+ 'spot': false,
32
+ 'margin': false,
33
+ 'swap': false,
34
+ 'future': false,
35
+ 'option': false,
36
+ 'approve': true,
37
+ 'cancelAllOrders': true,
38
+ 'cancelOrder': true,
39
+ 'cancelOrders': true,
40
+ 'createOrder': true,
41
+ 'fetchBalance': true,
42
+ 'fetchClosedOrders': false,
43
+ 'fetchEvent': true,
44
+ 'fetchEvents': true,
45
+ 'fetchMarkets': true,
46
+ 'fetchMyTrades': true,
47
+ 'fetchOHLCV': false,
48
+ 'fetchOpenOrders': true,
49
+ 'fetchOrder': true,
50
+ 'fetchOrderBook': true,
51
+ 'fetchOrders': true,
52
+ 'fetchPositions': true,
53
+ 'fetchSettlements': true,
54
+ 'fetchTicker': true,
55
+ 'fetchTickers': true,
56
+ 'fetchTrades': true,
57
+ 'prediction': true,
58
+ 'watchMyTrades': true,
59
+ 'watchOrderBook': true,
60
+ 'watchOrders': true,
61
+ 'watchTicker': true,
62
+ 'watchTrades': true,
63
+ },
64
+ 'urls': {
65
+ 'logo': 'https://github.com/user-attachments/assets/6d1d1e37-2b9c-4011-93c6-c9c25cd2c2f7',
66
+ 'api': {
67
+ 'sxbet': 'https://api.sx.bet',
68
+ 'ws': 'wss://realtime.sx.bet/connection/websocket',
69
+ },
70
+ 'test': {
71
+ 'sxbet': 'https://api.toronto.sx.bet',
72
+ 'ws': 'wss://realtime.toronto.sx.bet/connection/websocket',
73
+ },
74
+ 'www': 'https://sx.bet',
75
+ 'doc': ['https://docs.sx.bet'],
76
+ },
77
+ 'api': {
78
+ 'sxbet': {
79
+ 'public': {
80
+ 'get': {
81
+ 'metadata/obv3': 1,
82
+ 'orderbook-v3/snapshot': 1,
83
+ 'trades-v3/public': 1,
84
+ 'markets/active': 1,
85
+ 'markets/find': 1,
86
+ 'markets/popular': 1,
87
+ 'trades/consolidated': 1,
88
+ 'trades/orders': 1,
89
+ 'trades/portfolio/refunds': 1,
90
+ 'fixture/active': 1,
91
+ 'fixture/status': 1,
92
+ 'sports': 1,
93
+ 'leagues': 1,
94
+ 'leagues/active': 1,
95
+ 'teams': 1,
96
+ 'live-scores': 1,
97
+ },
98
+ },
99
+ 'private': {
100
+ 'get': {
101
+ 'user/realtime-token-v3/api-key': 1,
102
+ 'user/proxy': 1,
103
+ 'user/balance-v3': 1,
104
+ 'user/transfer-to-proxy/pending': 1,
105
+ 'user/transfer-to-proxy/status': 1,
106
+ 'orders-v3': 1,
107
+ 'orders-v3/{orderId}': 1,
108
+ 'orders-v3/odds/best': 1,
109
+ 'trades-v3': 1,
110
+ 'fills-v3': 1,
111
+ 'positions-v3': 1,
112
+ },
113
+ 'delete': {
114
+ 'orders-v3': 1,
115
+ 'orders-v3/event': 1,
116
+ 'orders-v3/all': 1,
117
+ },
118
+ 'post': {
119
+ 'orders-v3': 1,
120
+ 'user/deploy-proxy': 1,
121
+ 'user/transfer-to-proxy': 1,
122
+ 'heartbeat/v3': 1,
123
+ },
124
+ },
125
+ },
126
+ },
127
+ 'requiredCredentials': {
128
+ // apiKey is the x-sx-api-key header - every private v3 route authenticates with it;
129
+ // trading additionally needs walletAddress (order 'maker' field) and privateKey
130
+ 'apiKey': true,
131
+ 'secret': false,
132
+ 'walletAddress': true,
133
+ 'privateKey': true,
134
+ },
135
+ 'fees': {
136
+ 'trading': {
137
+ 'tierBased': false,
138
+ 'percentage': true,
139
+ // per-account fee rates live behind GET /user/fees-v3 and default to zero;
140
+ // there is no public venue-wide maker/taker schedule to mirror here
141
+ 'maker': 0.0,
142
+ 'taker': 0.0,
143
+ },
144
+ },
145
+ 'exceptions': {
146
+ // sx.bet errors come as { "error": "Bad Request", "message": <CODE or [strings]>, "statusCode": 4xx };
147
+ // per-order statuses inside a 200 createOrder response reuse the same codes
148
+ 'exact': {
149
+ 'INSUFFICIENT_BALANCE': InsufficientFunds, // maker token balance too low
150
+ 'INVALID_MARKET': BadRequest, // non-unique marketHashes in one request
151
+ 'ORDERS_ALREADY_EXIST': DuplicateOrderId,
152
+ 'TOO_MANY_DIFFERENT_MARKETS': BadRequest, // more than 3 markets in a single request
153
+ 'ORDERS_MUST_HAVE_IDENTICAL_MARKET': BadRequest,
154
+ 'BAD_BASE_TOKEN': BadRequest, // all orders must share one base token
155
+ 'INSUFFICIENT_KYC': PermissionDenied,
156
+ 'PROXY_NOT_DEPLOYED': AccountNotEnabled, // live-verified v3: the account's obv3 proxy wallet is not deployed yet - call approve() (deploys and funds it)
157
+ 'INVALID_USER': AuthenticationError, // live-verified v3: the x-sx-api-key does not belong to a registered user on the target network
158
+ 'BAD_AUTH': AuthenticationError, // live-verified v3: missing or malformed x-sx-api-key
159
+ 'ERC20_PERMIT_BAD_SPENDER': BadRequest, // live-verified v3: transfer-to-proxy permit signed for the wrong executor
160
+ 'BASE_TOKENS_NOT_SAME': BadRequest,
161
+ 'MARKETS_NOT_SAME': BadRequest,
162
+ 'DIRECTIONS_NOT_SAME': BadRequest,
163
+ 'INVALID_ORDERS': OrderNotFillable, // a targeted order is no longer active
164
+ 'INVALID_ODDS': InvalidOrder, // desiredOdds must be below 10^20
165
+ 'PERCENTAGEODDS_UNDEFINED_OR_MALFORMED': InvalidOrder, // live-verified: maker percentageOdds above the 10^20 cap (price > 1) or malformed
166
+ 'TOTAL_BET_SIZE_TOO_LOW': InvalidOrder, // live-verified: stake below the venue's minimum bet size
167
+ 'MATCH_STATE_INVALID': MarketClosed, // fixture not available for betting
168
+ 'PROXY_ACCOUNT_INVALID': BadRequest,
169
+ 'RATE_LIMIT_ORDER_REQUEST_MARKET_COUNT': BadRequest, // more than 1000 marketHashes queried
170
+ 'BOTH_SPORTXEVENTID_MARKETHASHES_PRESENT': BadRequest,
171
+ 'BAD_MARKET_HASHES': BadRequest, // invalid or more than 30 hashes on /markets/find
172
+ 'Order not found': OrderNotFound, // live-verified v3: GET /orders-v3/{orderId} 404s for a missing or foreign id
173
+ },
174
+ 'broad': {
175
+ 'no longer supported': NotSupported, // e.g. "OrderBook V2 is no longer supported" (testnet V3 canary gate)
176
+ 'must be': BadRequest, // request validation messages like "orders must be an array"
177
+ 'Insufficient available balance': InsufficientFunds, // live-verified v3: the proxy wallet holds less than the order size
178
+ },
179
+ },
180
+ 'options': {
181
+ 'marketsPageSize': 100, // markets per GET /markets/active page (cursor pagination)
182
+ 'maxMarketsPages': 50, // safety cap on pages fetched per markets scan (100 * 50 = 5000 markets)
183
+ // venue-specific fetchEvents scope params accepted by requireEventQuery
184
+ 'eventScopeParams': ['leagueId', 'sportId'],
185
+ // per-chain RPC endpoint for the on-chain reads approve() needs (ERC20 name()/nonces());
186
+ // sx.bet's own REST API has no RPC proxy, and /metadata carries no rpcUrl field
187
+ 'chains': {
188
+ '4162': { 'rpcUrl': 'https://rpc-rollup.sx.technology' },
189
+ '79479957': { 'rpcUrl': 'https://rpc-rollup.toronto.sx.technology' },
190
+ },
191
+ 'approveDeadlineSeconds': 7200,
192
+ // the venue rejects expiry 0 and past values - default resting orders to one day
193
+ 'defaultOrderExpirySeconds': 86400,
194
+ // the venue caps GET /orders-v3/odds/best at 100 market hashes per request
195
+ 'bestOddsBatchSize': 100,
196
+ // the venue caps DELETE /orders-v3 at maxCancelOrders (100, see /metadata/obv3) ids per request
197
+ 'cancelOrdersBatchSize': 100,
198
+ 'tradesLimit': 1000,
199
+ 'ordersLimit': 1000,
200
+ 'myTradesLimit': 1000,
201
+ },
202
+ 'streaming': {
203
+ // Centrifugo server pings arrive shaped like empty frames and are answered by pong();
204
+ // no client-initiated keepAlive is needed
205
+ 'keepAlive': 30000,
206
+ },
207
+ });
208
+ }
209
+ /**
210
+ * @method
211
+ * @name sxbet#fetchMarkets
212
+ * @description retrieves data on all active markets, each becomes one market with its two sides listed under the outcomes key
213
+ * @see https://docs.sx.bet/api-reference/get-markets-active
214
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
215
+ * @param {int} [params.limit] max number of markets to collect (defaults to options.marketsPageSize * options.maxMarketsPages, 5000)
216
+ * @returns {object[]} an array of objects representing market data
217
+ */
218
+ async fetchMarkets(params = {}) {
219
+ const rest = this.omit(params, ['limit']);
220
+ const userLimit = this.safeInteger(params, 'limit');
221
+ const rawMarkets = await this.fetchRawMarketsPaged(rest, userLimit);
222
+ const markets = [];
223
+ const rawMarketsLength = rawMarkets.length;
224
+ for (let i = 0; i < rawMarketsLength; i++) {
225
+ markets.push(this.parseSxbetMarket(rawMarkets[i]));
226
+ }
227
+ const marketsLength = markets.length;
228
+ if ((userLimit !== undefined) && (marketsLength > userLimit)) {
229
+ return this.arraySlice(markets, 0, userLimit);
230
+ }
231
+ return markets;
232
+ }
233
+ /**
234
+ * @ignore
235
+ * @method
236
+ * @name sxbet#fetchRawMarketsPaged
237
+ * @description pages through GET /markets/active (cursor-based via paginationKey/nextKey), stopping once options.maxMarketsPages pages or userLimit raw markets have been collected
238
+ * @param {object} [extra] extra request params merged into every page (e.g. leagueId, sportId, sportXeventId)
239
+ * @param {int} [userLimit] stop collecting once this many raw markets have been gathered
240
+ * @returns {object[]} the raw (unparsed) sx.bet market objects
241
+ */
242
+ async fetchRawMarketsPaged(extra = {}, userLimit = undefined) {
243
+ const pageSize = this.safeInteger(this.options, 'marketsPageSize', 100);
244
+ const maxPages = this.safeInteger(this.options, 'maxMarketsPages', 50);
245
+ const rawMarkets = [];
246
+ let paginationKey = undefined;
247
+ let page = 0;
248
+ while (true) {
249
+ const request = { 'pageSize': pageSize };
250
+ if (paginationKey !== undefined) {
251
+ request['paginationKey'] = paginationKey;
252
+ }
253
+ const response = await this.sxbetPublicGetMarketsActive(this.extend(request, extra));
254
+ const result = this.safeDict(response, 'data', {});
255
+ const pageMarkets = this.safeList(result, 'markets', []);
256
+ const pageMarketsLength = pageMarkets.length;
257
+ for (let i = 0; i < pageMarketsLength; i++) {
258
+ rawMarkets.push(pageMarkets[i]);
259
+ }
260
+ paginationKey = this.safeString(result, 'nextKey');
261
+ page = this.sum(page, 1);
262
+ const collectedLength = rawMarkets.length;
263
+ if ((pageMarketsLength < pageSize) || (page >= maxPages) || (paginationKey === undefined) || ((userLimit !== undefined) && (collectedLength >= userLimit))) {
264
+ break;
265
+ }
266
+ }
267
+ return rawMarkets;
268
+ }
269
+ /**
270
+ * @ignore
271
+ * @method
272
+ * @name sxbet#filterRawMarketsByFixture
273
+ * @description keeps only the raw markets belonging to one fixture — the venue's own sportXeventId filter on /markets/active is unreliable (observed live returning every fixture), so the scope is enforced client-side
274
+ * @param {object[]} rawMarkets the raw sx.bet market objects
275
+ * @param {string} sportXeventId the fixture id to keep
276
+ * @returns {object[]} the raw markets of that fixture only
277
+ */
278
+ filterRawMarketsByFixture(rawMarkets, sportXeventId) {
279
+ const result = [];
280
+ const rawMarketsLength = rawMarkets.length;
281
+ for (let i = 0; i < rawMarketsLength; i++) {
282
+ const raw = rawMarkets[i];
283
+ if (this.safeString(raw, 'sportXeventId') === sportXeventId) {
284
+ result.push(raw);
285
+ }
286
+ }
287
+ return result;
288
+ }
289
+ /**
290
+ * @ignore
291
+ * @method
292
+ * @name sxbet#parseSxbetMarket
293
+ * @description converts a single raw sx.bet market into one ccxt market whose two sides become the outcomes
294
+ * @param {object} raw the raw sx.bet market object
295
+ * @returns {object} a [market structure](https://docs.ccxt.com/#/?id=market-structure)
296
+ */
297
+ parseSxbetMarket(raw) {
298
+ //
299
+ // {
300
+ // "status": "ACTIVE",
301
+ // "marketHash": "0x7154aa3580267e276cccd0f4f826464a05e3efd8e1c81d7717bef6b5ae88b07d",
302
+ // "outcomeOneName": "Los Angeles Rams",
303
+ // "outcomeTwoName": "San Francisco 49ers",
304
+ // "outcomeVoidName": "NO_CONTEST",
305
+ // "teamOneName": "Los Angeles Rams",
306
+ // "teamTwoName": "San Francisco 49ers",
307
+ // "type": 226,
308
+ // "gameTime": 1789086900,
309
+ // "line": -2.5, // present on spread/total markets only
310
+ // "sportXeventId": "L18870109",
311
+ // "liveEnabled": true,
312
+ // "sportLabel": "Football",
313
+ // "sportId": 8,
314
+ // "leagueId": 243,
315
+ // "leagueLabel": "NFL",
316
+ // "mainLine": true,
317
+ // "isQuarterLineMarket": false
318
+ // }
319
+ //
320
+ const marketHash = this.safeString(raw, 'marketHash', '');
321
+ const teamOneName = this.safeString(raw, 'teamOneName');
322
+ const teamTwoName = this.safeString(raw, 'teamTwoName');
323
+ const outcomeOneName = this.safeString(raw, 'outcomeOneName');
324
+ const outcomeTwoName = this.safeString(raw, 'outcomeTwoName');
325
+ const eventSlug = this.shortenSlug(teamOneName + ' ' + teamTwoName);
326
+ // one fixture carries many markets (moneyline, several spread/total lines, quarter/half
327
+ // variants) whose outcomeOneName text can coincide or nearly coincide, so a text-only
328
+ // slug isn't guaranteed unique. suffix with the market hash instead (always unique,
329
+ // letters+digits only so it survives shortenSlug in one atomic word)
330
+ // parseToInt-wrapped .length: the bare `const n = str.length;` statement is the php
331
+ // transpiler's ARRAY hint (count()), which breaks on a string — this form emits
332
+ // strlen()/len() correctly in both python and php
333
+ const hashLength = this.parseToInt(marketHash.length);
334
+ const hashSuffix = marketHash.slice(hashLength - 6);
335
+ const marketSlug = this.shortenSlug(outcomeOneName) + '_' + hashSuffix;
336
+ const marketSymbol = this.slugToMarketSymbol(eventSlug, marketSlug);
337
+ const status = this.safeStringUpper(raw, 'status');
338
+ const active = (status === 'ACTIVE');
339
+ // guard against a zero sentinel for "no scheduled game time" - safeTimestamp would
340
+ // turn it into the 1970 epoch
341
+ let gameTime = undefined;
342
+ if (this.safeInteger(raw, 'gameTime', 0) !== 0) {
343
+ gameTime = this.safeTimestamp(raw, 'gameTime');
344
+ }
345
+ const outcomeLabels = [outcomeOneName, outcomeTwoName];
346
+ const outcomeIds = [marketHash, marketHash + '-2'];
347
+ const outcomes = [];
348
+ for (let oi = 0; oi < outcomeLabels.length; oi++) {
349
+ const label = outcomeLabels[oi];
350
+ const outcomeHandle = this.slugToOutcomeSymbol(eventSlug, marketSlug, label);
351
+ outcomes.push({
352
+ 'id': outcomeIds[oi],
353
+ 'outcomeId': outcomeIds[oi],
354
+ 'outcome': outcomeHandle,
355
+ 'market': marketSymbol,
356
+ 'label': label,
357
+ 'active': active,
358
+ 'winner': undefined,
359
+ 'settleFraction': undefined,
360
+ 'info': raw,
361
+ });
362
+ }
363
+ return {
364
+ 'id': marketHash,
365
+ 'market': marketSymbol,
366
+ 'base': 'USDC',
367
+ 'quote': 'USDC',
368
+ 'settle': undefined,
369
+ 'baseId': marketHash,
370
+ 'quoteId': 'USDC',
371
+ 'settleId': undefined,
372
+ 'type': 'prediction',
373
+ 'marketType': 'binary',
374
+ 'executionModel': 'clob',
375
+ 'spot': false,
376
+ 'margin': false,
377
+ 'swap': false,
378
+ 'future': false,
379
+ 'option': false,
380
+ 'prediction': true,
381
+ 'active': active,
382
+ 'resolved': false,
383
+ 'resolvedOutcome': undefined,
384
+ 'contract': false,
385
+ 'linear': undefined,
386
+ 'inverse': undefined,
387
+ 'contractSize': undefined,
388
+ 'expiry': gameTime,
389
+ 'expiryDatetime': this.iso8601(gameTime),
390
+ 'strike': undefined,
391
+ 'optionType': undefined,
392
+ // no trading-fee percentage found anywhere in the docs/metadata (oracleFees is a
393
+ // resolution/oracle fee, not a per-trade maker/taker cut) - 0 until live data says otherwise
394
+ 'taker': 0,
395
+ 'maker': 0,
396
+ 'percentage': true,
397
+ 'tierBased': false,
398
+ 'feeSide': 'get',
399
+ 'precision': {
400
+ // USDC stakes have 6 decimals; prices sit on the odds ladder (oddsLadderStepSize
401
+ // 125 in 1e-5 units on both networks — see /metadata), i.e. a 0.00125 step
402
+ 'amount': 0.000001,
403
+ 'price': 0.00125,
404
+ },
405
+ 'limits': {
406
+ 'leverage': { 'min': 1, 'max': 1 },
407
+ // /metadata/obv3 publishes limits.orderSizeMinimum '1' (1 USDC, 1000000 base units)
408
+ // on both networks - the stake and its cost share the same floor
409
+ 'amount': { 'min': 1, 'max': undefined },
410
+ 'price': { 'min': 0, 'max': 1 },
411
+ 'cost': { 'min': 1, 'max': undefined },
412
+ },
413
+ 'outcomes': outcomes,
414
+ 'info': raw,
415
+ 'created': undefined,
416
+ };
417
+ }
418
+ /**
419
+ * @method
420
+ * @name sxbet#fetchEvents
421
+ * @description fetches sx.bet fixtures (one fixture = one event, its markets are every moneyline/spread/total line on that fixture) scoped by eventId, leagueId, sportId or a free-text query/tags match against team and league names — always live from the API, never the local cache (it POPULATES the cache for later event()/outcome lookups). query/queries/tags are matched client-side over a bounded scan of /markets/active — the venue's GET /search covers team names only (not league or sport labels) and is not wired here yet
422
+ * @see https://docs.sx.bet/api-reference/get-markets-active
423
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
424
+ * @param {string} [params.eventId] direct lookup by unified event id (the sx.bet sportXeventId, e.g. 'L18870109')
425
+ * @param {string} [params.query] free-text search matched against team and league names
426
+ * @param {string[]} [params.queries] multiple free-text searches (alternative to query, unioned)
427
+ * @param {string[]} [params.tags] matched identically to query/queries (sx.bet has no tag taxonomy)
428
+ * @param {int} [params.leagueId] sx.bet league id (e.g. 243 for NFL) — fetched server-side
429
+ * @param {int} [params.sportId] sx.bet sport id (e.g. 8 for Football) — fetched server-side
430
+ * @param {string} [params.status] 'active' | 'inactive' | 'closed' | 'all'
431
+ * @param {int} [params.limit] max number of events to return
432
+ * @returns {object[]} an array of event structures
433
+ */
434
+ async fetchEvents(params = {}) {
435
+ this.requireEventQuery(params);
436
+ const eventId = this.safeString2(params, 'eventId', 'slug');
437
+ const leagueId = this.safeString(params, 'leagueId');
438
+ const sportId = this.safeString(params, 'sportId');
439
+ const queries = this.parseSearchQueries(params);
440
+ const tags = this.safeList(params, 'tags', []);
441
+ const tagsLength = tags.length;
442
+ for (let i = 0; i < tagsLength; i++) {
443
+ queries.push(tags[i]);
444
+ }
445
+ const rest = this.omit(params, ['eventId', 'slug', 'leagueId', 'sportId', 'query', 'queries', 'tags', 'status', 'sort', 'searchIn', 'limit']);
446
+ let rawMarkets;
447
+ if (eventId !== undefined) {
448
+ rawMarkets = await this.fetchRawMarketsPaged(this.extend({ 'sportXeventId': eventId }, rest), undefined);
449
+ // the venue's sportXeventId filter on /markets/active is unreliable (observed live
450
+ // returning every fixture) — enforce the scope client-side
451
+ rawMarkets = this.filterRawMarketsByFixture(rawMarkets, eventId);
452
+ }
453
+ else if (leagueId !== undefined) {
454
+ rawMarkets = await this.fetchRawMarketsPaged(this.extend({ 'leagueId': leagueId }, rest), undefined);
455
+ }
456
+ else if (sportId !== undefined) {
457
+ rawMarkets = await this.fetchRawMarketsPaged(this.extend({ 'sportId': sportId }, rest), undefined);
458
+ }
459
+ else {
460
+ // no server-side scope left, only query/tags — full scan honoring the fetchMarkets
461
+ // bound, then filter client-side (the venue's /search covers team names only)
462
+ rawMarkets = await this.fetchRawMarketsPaged(rest, undefined);
463
+ }
464
+ const queriesLength = queries.length;
465
+ if (queriesLength > 0) {
466
+ const filtered = [];
467
+ const preFilterLength = rawMarkets.length;
468
+ for (let i = 0; i < preFilterLength; i++) {
469
+ if (this.matchesEventQuery(rawMarkets[i], queries)) {
470
+ filtered.push(rawMarkets[i]);
471
+ }
472
+ }
473
+ rawMarkets = filtered;
474
+ }
475
+ const grouped = {};
476
+ const order = [];
477
+ const rawMarketsLength = rawMarkets.length;
478
+ for (let i = 0; i < rawMarketsLength; i++) {
479
+ const raw = rawMarkets[i];
480
+ const sportXeventId = this.safeString(raw, 'sportXeventId');
481
+ if (sportXeventId === undefined) {
482
+ continue;
483
+ }
484
+ if (!(sportXeventId in grouped)) {
485
+ grouped[sportXeventId] = [];
486
+ order.push(sportXeventId);
487
+ }
488
+ grouped[sportXeventId].push(raw);
489
+ }
490
+ if (this.markets === undefined) {
491
+ this.markets = this.createSafeDictionary();
492
+ }
493
+ const result = [];
494
+ const orderLength = order.length;
495
+ for (let i = 0; i < orderLength; i++) {
496
+ const fixtureId = order[i];
497
+ const event = this.parseEvent(fixtureId, grouped[fixtureId]);
498
+ const evMarkets = this.safeList(event, 'markets', []);
499
+ const evMarketsLength = evMarkets.length;
500
+ for (let j = 0; j < evMarketsLength; j++) {
501
+ const m = evMarkets[j];
502
+ this.markets[m['market']] = m;
503
+ }
504
+ result.push(event);
505
+ }
506
+ this.populateOutcomes();
507
+ this.setEvents(result);
508
+ const postParams = this.omit(params, ['leagueId', 'sportId', 'query', 'queries', 'tags']);
509
+ return this.applyEventFetchParams(result, postParams, []);
510
+ }
511
+ /**
512
+ * @method
513
+ * @name sxbet#fetchEvent
514
+ * @description fetches a single sx.bet fixture (event) by its sportXeventId
515
+ * @see https://docs.sx.bet/api-reference/get-markets-active
516
+ * @param {string} id the sx.bet sportXeventId, e.g. 'L18870109'
517
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
518
+ * @returns {object} a [prediction event structure](https://docs.ccxt.com/#/?id=prediction-event-structure)
519
+ */
520
+ async fetchEvent(id, params = {}) {
521
+ let rawMarkets = await this.fetchRawMarketsPaged(this.extend({ 'sportXeventId': id }, params), undefined);
522
+ // enforce the fixture scope client-side — see filterRawMarketsByFixture
523
+ rawMarkets = this.filterRawMarketsByFixture(rawMarkets, id);
524
+ const rawMarketsLength = rawMarkets.length;
525
+ if (rawMarketsLength === 0) {
526
+ throw new BadSymbol(this.id + ' fetchEvent() could not find an active fixture ' + id);
527
+ }
528
+ const event = this.parseEvent(id, rawMarkets);
529
+ this.indexEventOutcomes(event);
530
+ this.setEvents([event]);
531
+ return event;
532
+ }
533
+ /**
534
+ * @ignore
535
+ * @method
536
+ * @name sxbet#matchesEventQuery
537
+ * @description checks a raw market's team/league names against a list of free-text queries, matching in either direction (a short user query inside a long name, or — since fetchEvents derives its fallback test query from an already-slugified handle — a long joined query containing a short name)
538
+ * @param {object} raw the raw sx.bet market object
539
+ * @param {string[]} queries lowercase-insensitive free-text queries
540
+ * @returns {boolean} whether any query matches any of the market's team/league/outcome names
541
+ */
542
+ matchesEventQuery(raw, queries) {
543
+ const fields = [
544
+ this.safeString(raw, 'teamOneName'),
545
+ this.safeString(raw, 'teamTwoName'),
546
+ this.safeString(raw, 'leagueLabel'),
547
+ this.safeString(raw, 'sportLabel'),
548
+ this.safeString(raw, 'outcomeOneName'),
549
+ this.safeString(raw, 'outcomeTwoName'),
550
+ ];
551
+ const queriesLength = queries.length;
552
+ for (let qi = 0; qi < queriesLength; qi++) {
553
+ const query = queries[qi].toLowerCase();
554
+ if (query === '') {
555
+ continue;
556
+ }
557
+ for (let fi = 0; fi < fields.length; fi++) {
558
+ const field = fields[fi];
559
+ if (field === undefined) {
560
+ continue;
561
+ }
562
+ const fieldLower = field.toLowerCase();
563
+ if ((query.indexOf(fieldLower) >= 0) || (fieldLower.indexOf(query) >= 0)) {
564
+ return true;
565
+ }
566
+ }
567
+ }
568
+ return false;
569
+ }
570
+ /**
571
+ * @ignore
572
+ * @method
573
+ * @name sxbet#parseEvent
574
+ * @description groups a fixture's raw markets (all sharing one sportXeventId) into one unified event structure
575
+ * @param {string} fixtureId the sx.bet sportXeventId
576
+ * @param {object[]} rawMarkets the raw sx.bet market objects belonging to this fixture
577
+ * @returns {object} an event structure
578
+ */
579
+ parseEvent(fixtureId, rawMarkets) {
580
+ const marketsList = [];
581
+ let anyActive = false;
582
+ let earliestGameTime = undefined;
583
+ let teamOneName = undefined;
584
+ let teamTwoName = undefined;
585
+ let leagueLabel = undefined;
586
+ const rawMarketsLength = rawMarkets.length;
587
+ for (let i = 0; i < rawMarketsLength; i++) {
588
+ const raw = rawMarkets[i];
589
+ const parsed = this.parseSxbetMarket(raw);
590
+ if (parsed === undefined) {
591
+ throw new ExchangeError(this.id + ' parseEvent() could not resolve parsed market');
592
+ }
593
+ marketsList.push(parsed);
594
+ if (parsed['active'] === true) {
595
+ anyActive = true;
596
+ }
597
+ if (teamOneName === undefined) {
598
+ teamOneName = this.safeString(raw, 'teamOneName');
599
+ teamTwoName = this.safeString(raw, 'teamTwoName');
600
+ leagueLabel = this.safeString(raw, 'leagueLabel');
601
+ }
602
+ const gameTime = this.safeString(raw, 'gameTime');
603
+ // skip the zero sentinel for "no scheduled game time" so it can't become the epoch
604
+ if ((gameTime !== undefined) && (gameTime !== '0') && ((earliestGameTime === undefined) || (gameTime < earliestGameTime))) {
605
+ earliestGameTime = gameTime;
606
+ }
607
+ }
608
+ const end = this.safeTimestamp({ 'gameTime': earliestGameTime }, 'gameTime');
609
+ // some fixtures carry no team names on their market rows - fall back to the fixture id
610
+ // so the unified event handle is never empty or built from stringified nulls
611
+ let title = undefined;
612
+ let eventSlug = undefined;
613
+ if ((teamOneName !== undefined) && (teamTwoName !== undefined)) {
614
+ title = teamOneName + ' vs ' + teamTwoName;
615
+ eventSlug = this.shortenSlug(teamOneName + ' ' + teamTwoName);
616
+ }
617
+ else {
618
+ eventSlug = this.shortenSlug(fixtureId);
619
+ }
620
+ return {
621
+ 'id': fixtureId,
622
+ 'slug': fixtureId,
623
+ 'event': eventSlug,
624
+ 'title': title,
625
+ 'description': undefined,
626
+ 'category': leagueLabel,
627
+ 'tags': undefined,
628
+ 'markets': marketsList,
629
+ 'mutuallyExclusive': false,
630
+ 'active': anyActive,
631
+ 'resolved': undefined,
632
+ 'volume': undefined,
633
+ 'liquidity': undefined,
634
+ 'created': undefined,
635
+ 'createdDatetime': undefined,
636
+ 'end': end,
637
+ 'endDatetime': this.iso8601(end),
638
+ 'image': undefined,
639
+ 'url': undefined,
640
+ 'info': rawMarkets,
641
+ };
642
+ }
643
+ /**
644
+ * @ignore
645
+ * @method
646
+ * @name sxbet#loadSxObv3Metadata
647
+ * @description fetches and caches GET /metadata/obv3 - the v3 orderbook metadata carrying the ready-made EIP-712 domain, the active base asset and the order size limits
648
+ * @see https://docs.sx.bet/api-reference/get-metadata-obv3
649
+ * @returns {object} the cached obv3 metadata data object
650
+ */
651
+ async loadSxObv3Metadata() {
652
+ const cached = this.safeDict(this.options, 'sxObv3Metadata');
653
+ if (cached !== undefined) {
654
+ return cached;
655
+ }
656
+ const response = await this.sxbetPublicGetMetadataObv3();
657
+ const data = this.safeDict(response, 'data', {});
658
+ this.options['sxObv3Metadata'] = data;
659
+ return data;
660
+ }
661
+ /**
662
+ * @ignore
663
+ * @method
664
+ * @name sxbet#roundOddsToLadder
665
+ * @description rounds an implied probability (0-1) to sx.bet's odds ladder (oddsLadderStepSize is in units of 1e-5, e.g. 125 -> a 0.125% step) — a maker's percentageOdds is rejected unless it lands exactly on the ladder
666
+ * @param {string} probability the implied probability in decimal-string form (0-1)
667
+ * @param {string} oddsLadderStepSize the raw oddsLadderStepSize from /metadata/obv3 (e.g. '125')
668
+ * @returns {string} the probability rounded to the nearest ladder step, in decimal-string form
669
+ */
670
+ roundOddsToLadder(probability, oddsLadderStepSize) {
671
+ const tickSize = Precise.stringDiv(oddsLadderStepSize, '100000', 10);
672
+ return this.decimalToPrecision(probability, ROUND, tickSize, TICK_SIZE);
673
+ }
674
+ /**
675
+ * @ignore
676
+ * @method
677
+ * @name sxbet#hashEip712Digest
678
+ * @description hashes an EIP-712 encoded payload (domainSeparator‖structHash, prefixed with 0x1901 by ethEncodeStructuredData) down to the final 32-byte digest to sign, used by sx.bet's taker-fill and cancel signatures
679
+ * @param {Uint8Array} encoded the output of this.ethEncodeStructuredData (domain, types, message)
680
+ * @returns {string} the 32-byte digest to ecdsa-sign, in '0x'-prefixed hex form
681
+ */
682
+ hashEip712Digest(encoded) {
683
+ return '0x' + this.hash(encoded, keccak, 'hex');
684
+ }
685
+ /**
686
+ * @ignore
687
+ * @method
688
+ * @name sxbet#signDigest
689
+ * @description ecdsa-signs a 32-byte digest and assembles the r‖s‖v hex signature sx.bet expects
690
+ * @param {string} digest the '0x'-prefixed 32-byte digest to sign
691
+ * @param {string} privateKey the signer's private key
692
+ * @returns {string} a '0x'-prefixed 65-byte hex signature (r‖s‖v)
693
+ */
694
+ signDigest(digest, privateKey) {
695
+ const signature = ecdsa(digest.slice(-64), privateKey.slice(-64), secp256k1, undefined);
696
+ // assign to bare locals before padStart — the php transpiler's str_pad regex only
697
+ // matches a simple identifier, an expression form leaks a raw padStart() call
698
+ const rRaw = signature['r'];
699
+ const sRaw = signature['s'];
700
+ const r = rRaw.padStart(64, '0');
701
+ const s = sRaw.padStart(64, '0');
702
+ const v = this.intToBase16(this.sum(27, signature['v']));
703
+ return '0x' + r + s + v;
704
+ }
705
+ /**
706
+ * @ignore
707
+ * @method
708
+ * @name sxbet#fetchErc20Name
709
+ * @description reads an ERC20 token's name() via eth_call (needed for the Permit EIP-712 domain — must match the token's real on-chain name or the signature fails verification)
710
+ * @param {string} rpcUrl the RPC endpoint to call
711
+ * @param {string} tokenAddress the token contract address
712
+ * @returns {string} the token's on-chain name
713
+ */
714
+ async fetchErc20Name(rpcUrl, tokenAddress) {
715
+ const nameCallData = '0x06fdde03'; // name()
716
+ const result = await this.ethRpc(rpcUrl, 'eth_call', [{ 'to': tokenAddress, 'data': nameCallData }, 'latest']);
717
+ const hex = this.remove0xPrefix(result);
718
+ // dynamic ABI string return: [32-byte offset][32-byte length][utf8 bytes, right-padded]
719
+ const lengthHex = hex.slice(64, 128);
720
+ const length = this.hexToInt(lengthHex);
721
+ const dataEnd = this.sum(128, length * 2);
722
+ const dataHex = hex.slice(128, dataEnd);
723
+ return this.decode(this.base16ToBinary(dataHex));
724
+ }
725
+ /**
726
+ * @ignore
727
+ * @method
728
+ * @name sxbet#hexToInt
729
+ * @description parses a hex string (no '0x' prefix) into an integer — plain digit-by-digit, since JS's parseInt (hex, 16) two-argument form has no portable equivalent across languages/transpilers. Only used for small values (byte lengths, permit nonces), safe well within the float-precision range
730
+ * @param {string} hex the hex string, no '0x' prefix
731
+ * @returns {int} the parsed integer
732
+ */
733
+ hexToInt(hex) {
734
+ const digits = '0123456789abcdef';
735
+ const lowerHex = hex.toLowerCase();
736
+ const hexLength = lowerHex.length;
737
+ let result = 0;
738
+ for (let i = 0; i < hexLength; i++) {
739
+ const ch = lowerHex[i];
740
+ const digitValue = digits.indexOf(ch);
741
+ result = (result * 16) + digitValue;
742
+ }
743
+ return result;
744
+ }
745
+ /**
746
+ * @ignore
747
+ * @method
748
+ * @name sxbet#fetchSxbetProxy
749
+ * @description fetches the account's obv3 proxy wallet state
750
+ * @see https://docs.sx.bet/api-reference/get-user-proxy
751
+ * @returns {object} the raw proxy data ({obv3ProxyWalletAddress, deployed, multisigSafeAddress})
752
+ */
753
+ async fetchSxbetProxy() {
754
+ const response = await this.sxbetPrivateGetUserProxy();
755
+ return this.safeDict(response, 'data', {});
756
+ }
757
+ /**
758
+ * @method
759
+ * @name sxbet#approve
760
+ * @description funds the account's obv3 proxy wallet - v3 trading capital must sit inside the proxy. Deploys the proxy first when absent, then moves USDC from the wallet into it via a gasless EIP-2612 Permit signature (POST /user/transfer-to-proxy)
761
+ * @see https://docs.sx.bet/api-reference/post-user-transfer-to-proxy
762
+ * @see https://docs.sx.bet/api-reference/post-user-deploy-proxy
763
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
764
+ * @param {float} [params.amount] the USDC amount to move into the proxy (required)
765
+ * @param {string} [params.tokenAddress] the token to transfer, defaults to the active base token
766
+ * @param {string} [params.spender] the transfer executor granted the permit, defaults to options.transferToProxySpender or the obv3 transferToProxyExecutorAddress
767
+ * @param {int} [params.deadline] unix seconds the permit signature expires at, defaults to options.approveDeadlineSeconds from now
768
+ * @param {string} [params.rpcUrl] overrides the chain's default RPC endpoint (see options.chains)
769
+ * @returns {object} a dict with the raw response and the transfer sessionId
770
+ */
771
+ async approve(params = {}) {
772
+ this.checkRequiredCredentials();
773
+ const amount = this.safeNumber(params, 'amount');
774
+ if (amount === undefined) {
775
+ throw new ArgumentsRequired(this.id + ' approve() requires params.amount - the USDC amount to move into the proxy wallet');
776
+ }
777
+ const proxy = await this.fetchSxbetProxy();
778
+ const deployed = this.safeBool(proxy, 'deployed', false);
779
+ if (deployed !== true) {
780
+ await this.sxbetPrivatePostUserDeployProxy();
781
+ // deployment is asynchronous - poll until the proxy exists
782
+ for (let i = 0; i < 30; i++) {
783
+ await this.sleep(2000);
784
+ const state = await this.fetchSxbetProxy();
785
+ if (this.safeBool(state, 'deployed', false)) {
786
+ break;
787
+ }
788
+ }
789
+ }
790
+ const obv3 = await this.loadSxObv3Metadata();
791
+ const activeAsset = this.safeDict(obv3, 'activeAsset', {});
792
+ const chainId = this.safeInteger(obv3, 'chainId');
793
+ const usdcAddress = this.safeString(activeAsset, 'baseToken');
794
+ // the permit spender is the venue's transfer-to-proxy executor, published in /metadata/obv3
795
+ const executorAddress = this.safeString(obv3, 'transferToProxyExecutorAddress');
796
+ const tokenAddress = this.safeString(params, 'tokenAddress', usdcAddress);
797
+ if (tokenAddress === undefined) {
798
+ throw new BadRequest(this.id + ' approve() could not resolve the base token address from /metadata/obv3');
799
+ }
800
+ let spender = undefined;
801
+ [spender, params] = this.handleOptionAndParams2(params, 'approve', 'spender', 'transferToProxySpender', executorAddress);
802
+ if (spender === undefined) {
803
+ throw new BadRequest(this.id + ' approve() could not resolve the transfer-to-proxy executor from /metadata/obv3 - pass params.spender');
804
+ }
805
+ const chains = this.safeDict(this.options, 'chains', {});
806
+ const chainConfig = this.safeDict(chains, this.numberToString(chainId), {});
807
+ const rpcUrl = this.safeString(params, 'rpcUrl', this.safeString(chainConfig, 'rpcUrl'));
808
+ if (rpcUrl === undefined) {
809
+ throw new ArgumentsRequired(this.id + ' approve() has no RPC endpoint configured for chainId ' + this.numberToString(chainId) + ' - pass params.rpcUrl');
810
+ }
811
+ const owner = this.walletAddress;
812
+ const nonceCallData = '0x7ecebe00' + this.padHexAddress(owner); // nonces(address)
813
+ const nonceResult = await this.ethRpc(rpcUrl, 'eth_call', [{ 'to': tokenAddress, 'data': nonceCallData }, 'latest']);
814
+ const nonceHex = this.hexToRlpBytes(nonceResult);
815
+ const nonce = (nonceHex === '') ? '0' : this.numberToString(this.hexToInt(nonceHex));
816
+ const tokenName = await this.fetchErc20Name(rpcUrl, tokenAddress);
817
+ const defaultDeadlineSeconds = this.safeInteger(this.options, 'approveDeadlineSeconds', 7200);
818
+ const deadline = this.safeInteger(params, 'deadline', this.sum(this.seconds(), defaultDeadlineSeconds));
819
+ const value = this.decimalToPrecision(Precise.stringMul(this.numberToString(amount), '1000000'), ROUND, 0, DECIMAL_PLACES);
820
+ const domain = { 'name': tokenName, 'version': '1', 'chainId': chainId, 'verifyingContract': tokenAddress };
821
+ const messageTypes = {
822
+ 'Permit': [
823
+ { 'name': 'owner', 'type': 'address' },
824
+ { 'name': 'spender', 'type': 'address' },
825
+ { 'name': 'value', 'type': 'uint256' },
826
+ { 'name': 'nonce', 'type': 'uint256' },
827
+ { 'name': 'deadline', 'type': 'uint256' },
828
+ ],
829
+ };
830
+ const messageData = { 'owner': owner, 'spender': spender, 'value': value, 'nonce': nonce, 'deadline': deadline };
831
+ const encoded = this.ethEncodeStructuredData(domain, messageTypes, messageData);
832
+ const digest = this.hashEip712Digest(encoded);
833
+ const signature = this.signDigest(digest, this.privateKey);
834
+ const request = {
835
+ 'owner': owner,
836
+ 'spender': spender,
837
+ 'tokenAddress': tokenAddress,
838
+ 'value': value,
839
+ 'deadline': this.numberToString(deadline),
840
+ 'signature': signature,
841
+ };
842
+ const rest = this.omit(params, ['amount', 'tokenAddress', 'deadline', 'rpcUrl']);
843
+ const response = await this.sxbetPrivatePostUserTransferToProxy(this.extend(request, rest));
844
+ const data = this.safeDict(response, 'data', {});
845
+ return {
846
+ 'info': response,
847
+ 'id': this.safeString(data, 'sessionId'),
848
+ };
849
+ }
850
+ /**
851
+ * @method
852
+ * @name sxbet#createOrder
853
+ * @description places an order on sx.bet's v3 unified orderbook - a 'limit' order rests with GTC time-in-force, a 'market' order fills immediately with IOC (or FOK via params.timeInForce). sx.bet has no shares - 'amount' is the USDC stake to risk, and 'price' is the implied probability (0-1) of the requested outcome. 'sell' bets the OPPOSITE outcome of the one requested (sx.bet is bilateral: there is no owned position to sell, only the complementary side of the same market)
854
+ * @see https://docs.sx.bet/api-reference/post-orders-v3
855
+ * @param {string} outcome unified outcome or outcome token id
856
+ * @param {string} type 'limit' (GTC resting order) or 'market' (IOC immediate fill)
857
+ * @param {string} side 'buy' backs the requested outcome, 'sell' backs the complementary one
858
+ * @param {float} amount the USDC amount to stake/risk
859
+ * @param {float} [price] implied probability (0-1) of the requested outcome; required for both order types
860
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
861
+ * @param {string} [params.timeInForce] overrides the derived value - 'GTC', 'IOC' or 'FOK'
862
+ * @param {int} [params.expiry] unix seconds the order expires at; must be in the future (zero and past values are rejected, so is anything inside the fixture's betting-delay window - /metadata/obv3 resolves the delay per sport/league, live vs pregame), defaults to options.defaultOrderExpirySeconds from now
863
+ * @param {string} [params.salt] overrides the random salt differentiating this order
864
+ * @param {string} [params.clientOrderId] caller-chosen id echoed back on reads (max 64 chars)
865
+ * @param {boolean} [params.waitForOutcome] wait for the matching outcome inline (default true)
866
+ * @param {boolean} [params.useBetCredits] fund the stake from bet credits instead of the proxy balance (IOC/FOK only)
867
+ * @param {string} [params.externalUserId] partner attribution id echoed back on order, fill and trade reads
868
+ * @returns {object} a [prediction order structure](https://docs.ccxt.com/#/?id=prediction-order-structure)
869
+ */
870
+ async createOrder(outcome, type, side, amount, price = undefined, params = {}) {
871
+ this.checkRequiredCredentials();
872
+ await this.loadOutcome(outcome);
873
+ const outcomeObj = this.outcome(outcome);
874
+ if ((type !== 'limit') && (type !== 'market')) {
875
+ throw new InvalidOrder(this.id + " createOrder() type must be 'limit' or 'market', got " + type);
876
+ }
877
+ if (price === undefined) {
878
+ throw new ArgumentsRequired(this.id + ' createOrder() requires a price - the implied probability of the requested outcome');
879
+ }
880
+ // the venue has no post-only or trigger mechanics - reject the unified params instead
881
+ // of forwarding fields the exchange would silently ignore
882
+ const postOnly = this.safeBool(params, 'postOnly', false);
883
+ if (postOnly === true) {
884
+ throw new NotSupported(this.id + ' createOrder() does not support postOnly - GTC orders may cross on entry');
885
+ }
886
+ const triggerPrice = this.safeStringN(params, ['triggerPrice', 'stopLossPrice', 'takeProfitPrice']);
887
+ if (triggerPrice !== undefined) {
888
+ throw new NotSupported(this.id + ' createOrder() does not support trigger, stop-loss or take-profit orders');
889
+ }
890
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash', '');
891
+ const outcomeId = this.safeString(outcomeObj, 'outcomeId');
892
+ const isOutcomeOne = (outcomeId === marketHash);
893
+ const isBuy = (side === 'buy');
894
+ // 'sell' bets the complementary outcome, mirroring the requested outcome's own probability -
895
+ // matches the normalize-to-one-book convention used by other prediction venues
896
+ const isMakerBettingOutcomeOne = (isBuy) ? isOutcomeOne : !isOutcomeOne;
897
+ const priceStr = this.numberToString(price);
898
+ const probability = (isBuy) ? priceStr : Precise.stringSub('1', priceStr);
899
+ const obv3 = await this.loadSxObv3Metadata();
900
+ const domain = this.safeDict(obv3, 'domain', {});
901
+ const activeAsset = this.safeDict(obv3, 'activeAsset', {});
902
+ const baseToken = this.safeString(activeAsset, 'baseToken');
903
+ const oddsLadderStepSize = this.numberToString(this.safeInteger(obv3, 'oddsLadderStepSize', 125));
904
+ const roundedProbability = this.roundOddsToLadder(probability, oddsLadderStepSize);
905
+ const percentageOdds = this.decimalToPrecision(Precise.stringMul(roundedProbability, '100000000000000000000'), ROUND, 0, DECIMAL_PLACES);
906
+ const amountStr = this.numberToString(amount);
907
+ const totalBetSize = this.decimalToPrecision(Precise.stringMul(amountStr, '1000000'), ROUND, 0, DECIMAL_PLACES);
908
+ const saltNumber = this.safeString(params, 'salt', this.numberToString(this.milliseconds()));
909
+ // the request body carries the salt in 32-byte-hex form; the signed struct carries the
910
+ // same value in uint256 form - both parse to one number server-side. assign to bare locals
911
+ // before padStart - see the transpiler notes
912
+ const saltHexRaw = this.intToBase16(this.parseToInt(saltNumber));
913
+ const saltHexPadded = saltHexRaw.padStart(64, '0');
914
+ const saltHex = '0x' + saltHexPadded;
915
+ const defaultExpirySeconds = this.safeInteger(this.options, 'defaultOrderExpirySeconds', 86400);
916
+ const expiry = this.safeInteger(params, 'expiry', this.sum(this.seconds(), defaultExpirySeconds));
917
+ const defaultTif = (type === 'limit') ? 'GTC' : 'IOC';
918
+ let timeInForce = undefined;
919
+ [timeInForce, params] = this.handleOptionAndParams(params, 'createOrder', 'timeInForce', defaultTif);
920
+ // an explicit IOC/FOK on a 'limit' order is honored verbatim - the venue executes exactly
921
+ // that time-in-force. only GTC on a 'market' order is refused: it would silently rest,
922
+ // contradicting the immediate-fill semantics the type promises
923
+ if ((type === 'market') && (timeInForce === 'GTC')) {
924
+ throw new InvalidOrder(this.id + " createOrder() market orders cannot be GTC - use type 'limit' for a resting order");
925
+ }
926
+ const maker = this.walletAddress;
927
+ const messageTypes = {
928
+ 'Order': [
929
+ { 'name': 'marketHash', 'type': 'bytes32' },
930
+ { 'name': 'baseToken', 'type': 'address' },
931
+ { 'name': 'totalBetSize', 'type': 'uint256' },
932
+ { 'name': 'percentageOdds', 'type': 'uint256' },
933
+ { 'name': 'salt', 'type': 'uint256' },
934
+ { 'name': 'expiry', 'type': 'uint256' },
935
+ { 'name': 'maker', 'type': 'address' },
936
+ { 'name': 'isMakerBettingOutcomeOne', 'type': 'bool' },
937
+ ],
938
+ };
939
+ const messageData = {
940
+ 'marketHash': marketHash,
941
+ 'baseToken': baseToken,
942
+ 'totalBetSize': totalBetSize,
943
+ 'percentageOdds': percentageOdds,
944
+ 'salt': saltNumber,
945
+ 'expiry': expiry,
946
+ 'maker': maker,
947
+ 'isMakerBettingOutcomeOne': isMakerBettingOutcomeOne,
948
+ };
949
+ const encoded = this.ethEncodeStructuredData(domain, messageTypes, messageData);
950
+ const digest = this.hashEip712Digest(encoded);
951
+ const orderSignature = this.signDigest(digest, this.privateKey);
952
+ const orderItem = {
953
+ 'marketHash': marketHash,
954
+ 'maker': maker,
955
+ 'totalBetSize': totalBetSize,
956
+ 'percentageOdds': percentageOdds,
957
+ 'salt': saltHex,
958
+ 'expiry': expiry,
959
+ 'baseToken': baseToken,
960
+ 'isMakerBettingOutcomeOne': isMakerBettingOutcomeOne,
961
+ 'timeInForce': timeInForce,
962
+ 'orderSignature': orderSignature,
963
+ };
964
+ const clientOrderId = this.safeString(params, 'clientOrderId');
965
+ if (clientOrderId !== undefined) {
966
+ orderItem['clientOrderId'] = clientOrderId;
967
+ }
968
+ // useBetCredits and externalUserId are per-order fields - route them into the order item,
969
+ // not the top-level body, where the venue would silently ignore them
970
+ const useBetCredits = this.safeBool(params, 'useBetCredits');
971
+ if (useBetCredits !== undefined) {
972
+ orderItem['useBetCredits'] = useBetCredits;
973
+ }
974
+ const externalUserId = this.safeString(params, 'externalUserId');
975
+ if (externalUserId !== undefined) {
976
+ orderItem['externalUserId'] = externalUserId;
977
+ }
978
+ const waitForOutcome = this.safeBool(params, 'waitForOutcome', true);
979
+ const rest = this.omit(params, ['salt', 'expiry', 'clientOrderId', 'waitForOutcome', 'useBetCredits', 'externalUserId']);
980
+ const request = { 'orders': [orderItem], 'waitForOutcome': waitForOutcome };
981
+ const response = await this.sxbetPrivatePostOrdersV3(this.extend(request, rest));
982
+ const data = this.safeDict(response, 'data', {});
983
+ const results = this.safeList(data, 'orders', []);
984
+ const first = this.safeDict(results, 0, {});
985
+ const status = this.safeStringUpper(first, 'status');
986
+ if (status === 'FAILED') {
987
+ const reason = this.safeString(first, 'reason', 'FAILED');
988
+ const feedback = this.id + ' createOrder() rejected: ' + reason;
989
+ this.throwExactlyMatchedException(this.exceptions['exact'], reason, feedback);
990
+ throw new InvalidOrder(feedback);
991
+ }
992
+ // with waitForOutcome the venue reports the matching result inline:
993
+ // "outcome": { "state": "FULLY_FILLED", "fillAmount": "2000000", "remainingAmount": "0", "matchIds": [...] }
994
+ // documented states: RESTED, FULLY_FILLED, PARTIAL_FILL_DONE, PARTIAL_FILL_RESTED, TIMEOUT and
995
+ // CANCELLED with cancelReason NO_LIQUIDITY, INTERNAL_ERROR, ENGINE_SHUTDOWN, EXPIRED or INSUFFICIENT_BALANCE
996
+ const matchOutcome = this.safeDict(first, 'outcome');
997
+ const usdcDecimals = '1000000';
998
+ let filled = undefined;
999
+ let remaining = undefined;
1000
+ let orderStatus = undefined;
1001
+ if (matchOutcome !== undefined) {
1002
+ const fillAmountRaw = this.safeString(matchOutcome, 'fillAmount');
1003
+ const remainingRaw = this.safeString(matchOutcome, 'remainingAmount');
1004
+ if (fillAmountRaw !== undefined) {
1005
+ filled = this.parseNumber(Precise.stringDiv(fillAmountRaw, usdcDecimals, 6));
1006
+ }
1007
+ if (remainingRaw !== undefined) {
1008
+ remaining = this.parseNumber(Precise.stringDiv(remainingRaw, usdcDecimals, 6));
1009
+ }
1010
+ const state = this.safeStringUpper(matchOutcome, 'state');
1011
+ if ((state === 'RESTED') || (state === 'PARTIAL_FILL_RESTED')) {
1012
+ orderStatus = 'open';
1013
+ }
1014
+ else if (state === 'FULLY_FILLED') {
1015
+ orderStatus = 'closed';
1016
+ }
1017
+ else if (state === 'PARTIAL_FILL_DONE') {
1018
+ // the unmatched remainder of an IOC was cancelled by the venue
1019
+ orderStatus = 'canceled';
1020
+ }
1021
+ else if (state === 'CANCELLED') {
1022
+ // the documented field is cancelReason; 'reason' is kept for older responses
1023
+ const reason = this.safeStringUpper2(matchOutcome, 'cancelReason', 'reason');
1024
+ orderStatus = (reason === 'EXPIRED') ? 'expired' : 'canceled';
1025
+ }
1026
+ // TIMEOUT: the wait elapsed before matching resolved - the order state is unknown here
1027
+ }
1028
+ const now = this.milliseconds();
1029
+ return this.safePredictionOrder({
1030
+ 'id': this.safeString2(first, 'orderId', 'orderHash'),
1031
+ 'clientOrderId': this.safeString(first, 'clientOrderId', clientOrderId),
1032
+ 'info': response,
1033
+ 'timestamp': now,
1034
+ 'datetime': this.iso8601(now),
1035
+ 'status': orderStatus,
1036
+ 'filled': filled,
1037
+ 'remaining': remaining,
1038
+ 'cost': filled,
1039
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
1040
+ 'outcomeId': outcomeId,
1041
+ 'label': this.safeString(outcomeObj, 'label'),
1042
+ 'market': this.safeString(outcomeObj, 'market'),
1043
+ 'type': type,
1044
+ 'timeInForce': timeInForce,
1045
+ 'side': side,
1046
+ 'price': this.parseNumber(roundedProbability),
1047
+ 'amount': amount,
1048
+ 'fee': undefined,
1049
+ 'trades': [],
1050
+ }, outcomeObj);
1051
+ }
1052
+ /**
1053
+ * @ignore
1054
+ * @method
1055
+ * @name sxbet#parseSxbetCancelResponse
1056
+ * @description parses the shared v3 cancel response ({cancelled, notCancelled, unconfirmed}) into order structures
1057
+ * @param {object} response the raw DELETE /orders-v3 response
1058
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
1059
+ */
1060
+ parseSxbetCancelResponse(response) {
1061
+ const data = this.safeDict(response, 'data', {});
1062
+ const result = [];
1063
+ // by-id cancels report 'cancelled'; the /all and /event paths submit asynchronously and
1064
+ // report 'cancelledSubmitted' instead - treat both like cancelled
1065
+ const cancelled = this.safeList2(data, 'cancelled', 'cancelledSubmitted', []);
1066
+ const cancelledLength = cancelled.length;
1067
+ for (let i = 0; i < cancelledLength; i++) {
1068
+ result.push(this.safePredictionOrder({
1069
+ 'id': this.safeString(cancelled[i], 'orderId'),
1070
+ 'status': 'canceled',
1071
+ 'info': response,
1072
+ }));
1073
+ }
1074
+ const notCancelled = this.safeList(data, 'notCancelled', []);
1075
+ const notCancelledLength = notCancelled.length;
1076
+ for (let i = 0; i < notCancelledLength; i++) {
1077
+ // the venue reports why (e.g. NOT_FOUND) - the order was not cancelled, report it honestly
1078
+ result.push(this.safePredictionOrder({
1079
+ 'id': this.safeString(notCancelled[i], 'orderId'),
1080
+ 'status': undefined,
1081
+ 'info': response,
1082
+ }));
1083
+ }
1084
+ const unconfirmed = this.safeList(data, 'unconfirmed', []);
1085
+ const unconfirmedLength = unconfirmed.length;
1086
+ for (let i = 0; i < unconfirmedLength; i++) {
1087
+ result.push(this.safePredictionOrder({
1088
+ 'id': this.safeString(unconfirmed[i], 'orderId'),
1089
+ 'status': undefined,
1090
+ 'info': response,
1091
+ }));
1092
+ }
1093
+ return result;
1094
+ }
1095
+ /**
1096
+ * @method
1097
+ * @name sxbet#cancelOrder
1098
+ * @description cancels one resting maker order - v3 cancels are plain api-key-authenticated DELETE requests, no signature involved
1099
+ * @see https://docs.sx.bet/api-reference/delete-orders-v3
1100
+ * @param {string} id the order id
1101
+ * @param {string} [outcome] not used by sxbet.cancelOrder
1102
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1103
+ * @returns {object} a [prediction order structure](https://docs.ccxt.com/#/?id=prediction-order-structure)
1104
+ */
1105
+ async cancelOrder(id, outcome = undefined, params = {}) {
1106
+ this.checkRequiredCredentials();
1107
+ const request = { 'orders': [{ 'orderId': id }] };
1108
+ const response = await this.sxbetPrivateDeleteOrdersV3(this.extend(request, params));
1109
+ const orders = this.parseSxbetCancelResponse(response);
1110
+ return this.safeDict(orders, 0);
1111
+ }
1112
+ /**
1113
+ * @method
1114
+ * @name sxbet#cancelOrders
1115
+ * @description cancels multiple resting maker orders in one request - v3 cancels are plain api-key-authenticated DELETE requests, no signature involved
1116
+ * @see https://docs.sx.bet/api-reference/delete-orders-v3
1117
+ * @param {string[]} ids the order ids to cancel
1118
+ * @param {string} [outcome] not used by sxbet.cancelOrders
1119
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1120
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
1121
+ */
1122
+ async cancelOrders(ids, outcome = undefined, params = {}) {
1123
+ this.checkRequiredCredentials();
1124
+ const idsLength = ids.length;
1125
+ if (idsLength === 0) {
1126
+ throw new ArgumentsRequired(this.id + ' cancelOrders() requires a non-empty ids argument');
1127
+ }
1128
+ // the venue caps one cancel request at maxCancelOrders ids (100, see /metadata/obv3) -
1129
+ // chunk larger batches instead of letting the whole request 400
1130
+ const chunkSize = this.safeInteger(this.options, 'cancelOrdersBatchSize', 100);
1131
+ const chunkCount = this.parseToInt(this.sum(idsLength, chunkSize - 1) / chunkSize);
1132
+ let result = [];
1133
+ for (let c = 0; c < chunkCount; c++) {
1134
+ const start = c * chunkSize;
1135
+ let end = start + chunkSize;
1136
+ if (end > idsLength) {
1137
+ end = idsLength;
1138
+ }
1139
+ const orderItems = [];
1140
+ for (let i = start; i < end; i++) {
1141
+ orderItems.push({ 'orderId': ids[i] });
1142
+ }
1143
+ const request = { 'orders': orderItems };
1144
+ const response = await this.sxbetPrivateDeleteOrdersV3(this.extend(request, params));
1145
+ result = this.arrayConcat(result, this.parseSxbetCancelResponse(response));
1146
+ }
1147
+ return result;
1148
+ }
1149
+ /**
1150
+ * @method
1151
+ * @name sxbet#cancelAllOrders
1152
+ * @description cancels every resting maker order of the account, or every order of one fixture via params.eventId - v3 cancels are plain api-key-authenticated DELETE requests, no signature involved
1153
+ * @see https://docs.sx.bet/api-reference/delete-orders-v3-all
1154
+ * @see https://docs.sx.bet/api-reference/delete-orders-v3-event
1155
+ * @param {string} [outcome] not used by sxbet.cancelAllOrders
1156
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1157
+ * @param {string} [params.eventId] cancels every order across every market of this fixture instead of the account-wide path (params.sportXeventId is accepted too)
1158
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
1159
+ */
1160
+ async cancelAllOrders(outcome = undefined, params = {}) {
1161
+ this.checkRequiredCredentials();
1162
+ const eventId = this.safeString2(params, 'eventId', 'sportXeventId');
1163
+ const rest = this.omit(params, ['eventId', 'sportXeventId']);
1164
+ const isEventScoped = (eventId !== undefined);
1165
+ let response = undefined;
1166
+ if (isEventScoped) {
1167
+ // the event route takes eventId in the QUERY string, not the body - sign() urlencodes
1168
+ // body-less DELETE params
1169
+ const request = { 'eventId': eventId };
1170
+ response = await this.sxbetPrivateDeleteOrdersV3Event(this.extend(request, rest));
1171
+ }
1172
+ else {
1173
+ response = await this.sxbetPrivateDeleteOrdersV3All(rest);
1174
+ }
1175
+ let result = this.parseSxbetCancelResponse(response);
1176
+ // both paths paginate their async submission - keep going on the SAME route while more remain
1177
+ let hasMore = this.safeBool(this.safeDict(response, 'data', {}), 'hasMore', false);
1178
+ let guard = 0;
1179
+ while ((hasMore === true) && (guard < 50)) {
1180
+ let nextResponse = undefined;
1181
+ if (isEventScoped) {
1182
+ const nextRequest = { 'eventId': eventId };
1183
+ nextResponse = await this.sxbetPrivateDeleteOrdersV3Event(this.extend(nextRequest, rest));
1184
+ }
1185
+ else {
1186
+ nextResponse = await this.sxbetPrivateDeleteOrdersV3All(rest);
1187
+ }
1188
+ result = this.arrayConcat(result, this.parseSxbetCancelResponse(nextResponse));
1189
+ hasMore = this.safeBool(this.safeDict(nextResponse, 'data', {}), 'hasMore', false);
1190
+ guard = this.sum(guard, 1);
1191
+ }
1192
+ return result;
1193
+ }
1194
+ /**
1195
+ * @ignore
1196
+ * @method
1197
+ * @name sxbet#parsePredictionOrder
1198
+ * @description parses one raw GET /orders row into a unified prediction order. every sx.bet maker order is a 'buy' of the outcome it bets on (isMakerBettingOutcomeOne selects the side), priced at the maker's own implied probability
1199
+ * @param {object} order the raw sx.bet order object
1200
+ * @param {object} [market] the outcome object the order belongs to (resolved from the cache by outcomeId when omitted)
1201
+ * @returns {object} a [prediction order structure](https://docs.ccxt.com/#/?id=prediction-order-structure)
1202
+ */
1203
+ parsePredictionOrder(order, market = undefined) {
1204
+ //
1205
+ // {
1206
+ // "id": "0x112a4f19f20395aefcdb5d26f3f8aab0d96b1aff7ce346c0bfb13d9490340cdf",
1207
+ // "marketHash": "0xbf06c6379c922d8118612d1d7493b20f6df6929437fbf6022904327f9516a2eb",
1208
+ // "userAddress": "0xbcc6D643e4159A75ED1dB4e13330230B82F2AEe5",
1209
+ // "wallet": "0x4361123dbdc1D812fdf7D27045aF358C9C8AA70A",
1210
+ // "isBettingOutcomeOne": true,
1211
+ // "percentageOdds": "40000000000000000000",
1212
+ // "totalBetSize": "3000000",
1213
+ // "remainingSize": "3000000",
1214
+ // "expiry": null,
1215
+ // "status": "ACTIVE",
1216
+ // "inactiveReason": null,
1217
+ // "eventId": "L12003787",
1218
+ // "createdAt": "2026-07-31T18:45:42.636Z",
1219
+ // "updatedAt": "2026-07-31T18:45:42.838Z"
1220
+ // }
1221
+ //
1222
+ const orderId = this.safeString2(order, 'id', 'orderId');
1223
+ const marketHash = this.safeString(order, 'marketHash', '');
1224
+ const isBettingOutcomeOne = this.safeBool(order, 'isBettingOutcomeOne', true);
1225
+ const outcomeId = (isBettingOutcomeOne) ? marketHash : (marketHash + '-2');
1226
+ const outcomeObj = this.safeOutcome(outcomeId, market);
1227
+ const oneDenom = '100000000000000000000';
1228
+ const usdcDecimals = '1000000';
1229
+ const percentageOdds = this.safeString(order, 'percentageOdds');
1230
+ const price = (percentageOdds !== undefined) ? this.parseNumber(Precise.stringDiv(percentageOdds, oneDenom)) : undefined;
1231
+ const totalBetSize = this.safeString(order, 'totalBetSize', '0');
1232
+ const remainingSize = this.safeString(order, 'remainingSize', totalBetSize);
1233
+ const filledRaw = Precise.stringSub(totalBetSize, remainingSize);
1234
+ const amount = this.parseNumber(Precise.stringDiv(totalBetSize, usdcDecimals, 6));
1235
+ const filled = this.parseNumber(Precise.stringDiv(filledRaw, usdcDecimals, 6));
1236
+ const remaining = this.parseNumber(Precise.stringDiv(remainingSize, usdcDecimals, 6));
1237
+ const orderStatus = this.safeStringUpper(order, 'status');
1238
+ const inactiveReason = this.safeStringUpper(order, 'inactiveReason');
1239
+ let status = 'open';
1240
+ if (orderStatus === 'INACTIVE') {
1241
+ // documented inactiveReason enum: FILLED, USER_REQUESTED, EXPIRED, NO_LIQUIDITY,
1242
+ // INSUFFICIENT_BALANCE, EVENT_LIFECYCLE, MARKET_HALTED, HEARTBEAT_TIMEOUT, SYSTEM
1243
+ if (inactiveReason === 'FILLED') {
1244
+ status = 'closed';
1245
+ }
1246
+ else if (inactiveReason === 'EXPIRED') {
1247
+ status = 'expired';
1248
+ }
1249
+ else {
1250
+ status = 'canceled';
1251
+ }
1252
+ }
1253
+ const timestamp = this.parse8601(this.safeString(order, 'createdAt'));
1254
+ return this.safePredictionOrder({
1255
+ 'id': orderId,
1256
+ 'clientOrderId': this.safeString(order, 'clientOrderId'),
1257
+ 'timestamp': timestamp,
1258
+ 'datetime': this.iso8601(timestamp),
1259
+ 'lastTradeTimestamp': this.parse8601(this.safeString(order, 'updatedAt')),
1260
+ 'status': status,
1261
+ 'type': 'limit',
1262
+ 'timeInForce': undefined,
1263
+ 'side': 'buy',
1264
+ 'price': price,
1265
+ 'average': undefined,
1266
+ 'amount': amount,
1267
+ 'filled': filled,
1268
+ 'remaining': remaining,
1269
+ 'cost': undefined,
1270
+ 'fee': undefined,
1271
+ 'reduceOnly': undefined,
1272
+ 'postOnly': undefined,
1273
+ 'trades': [],
1274
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
1275
+ 'outcomeId': this.safeString(outcomeObj, 'outcomeId', outcomeId),
1276
+ 'label': this.safeString(outcomeObj, 'label'),
1277
+ 'market': this.safeString(outcomeObj, 'market'),
1278
+ 'info': order,
1279
+ }, outcomeObj);
1280
+ }
1281
+ /**
1282
+ * @ignore
1283
+ * @method
1284
+ * @name sxbet#clampSxbetPerPage
1285
+ * @description clamps a user limit to the venue's hard perPage ceiling - every v3 listing route rejects perPage above 100, and deeper history is reachable through the nextKey cursor instead
1286
+ * @param {int} limit the user-requested limit
1287
+ * @returns {int} the limit clamped to at most 100
1288
+ */
1289
+ clampSxbetPerPage(limit) {
1290
+ const maxPerPage = 100;
1291
+ if (limit === undefined) {
1292
+ return undefined;
1293
+ }
1294
+ return (limit > maxPerPage) ? maxPerPage : limit;
1295
+ }
1296
+ /**
1297
+ * @method
1298
+ * @name sxbet#fetchOpenOrders
1299
+ * @description fetches the account's resting maker orders via the api-key-authenticated GET /orders-v3 (the route is hardcoded to ACTIVE orders and scoped to the key's account)
1300
+ * @see https://docs.sx.bet/api-reference/get-orders-v3
1301
+ * @param {string} [outcome] unified outcome or outcomeId — narrows to that outcome's market
1302
+ * @param {int} [since] applied client-side (the route has no date filter; rows carry createdAt)
1303
+ * @param {int} [limit] the maximum number of orders to return (server-side perPage, max 100, default 50)
1304
+ * @param {object} [params] extra parameters specific to the exchange API endpoint (e.g. eventId, sortBy, sortAsc, nextKey)
1305
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
1306
+ */
1307
+ async fetchOpenOrders(outcome = undefined, since = undefined, limit = undefined, params = {}) {
1308
+ const request = {};
1309
+ let outcomeObj = undefined;
1310
+ if (outcome !== undefined) {
1311
+ await this.loadOutcome(outcome);
1312
+ outcomeObj = this.outcome(outcome);
1313
+ request['marketHash'] = this.safeString(outcomeObj['info'], 'marketHash');
1314
+ }
1315
+ if (limit !== undefined) {
1316
+ request['perPage'] = this.clampSxbetPerPage(limit);
1317
+ }
1318
+ const response = await this.sxbetPrivateGetOrdersV3(this.extend(request, params));
1319
+ const data = this.safeDict(response, 'data', {});
1320
+ const rawOrders = this.safeList(data, 'orders', []);
1321
+ return this.parsePredictionOrders(rawOrders, outcomeObj, since, limit);
1322
+ }
1323
+ /**
1324
+ * @method
1325
+ * @name sxbet#fetchOrders
1326
+ * @description fetches the account's maker orders. sx.bet's GET /orders-v3 listing is hardcoded to ACTIVE orders — filled/cancelled/expired orders leave the listing permanently (their history is only reconstructable from fills), so this returns the same set that fetchOpenOrders returns
1327
+ * @see https://docs.sx.bet/api-reference/get-orders-v3
1328
+ * @param {string} [outcome] unified outcome or outcomeId — narrows to that outcome's market
1329
+ * @param {int} [since] applied client-side (the route has no date filter; rows carry createdAt)
1330
+ * @param {int} [limit] the maximum number of orders to return (server-side perPage, max 100, default 50)
1331
+ * @param {object} [params] extra parameters specific to the exchange API endpoint (e.g. eventId, sortBy, sortAsc, nextKey)
1332
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
1333
+ */
1334
+ async fetchOrders(outcome = undefined, since = undefined, limit = undefined, params = {}) {
1335
+ return await this.fetchOpenOrders(outcome, since, limit, params);
1336
+ }
1337
+ /**
1338
+ * @method
1339
+ * @name sxbet#fetchOrder
1340
+ * @description fetches a single maker order by its order hash - unlike the listing, GET /orders-v3/{orderId} also serves filled, cancelled and expired orders while they still exist. a missing or foreign id 404s with 'Order not found', surfaced through handleErrors's OrderNotFound mapping
1341
+ * @see https://docs.sx.bet/api-reference/get-order-v3
1342
+ * @param {string} id the order hash
1343
+ * @param {string} [outcome] unified outcome or outcomeId (labelling hint only, the request needs just the id)
1344
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1345
+ * @returns {object} a [prediction order structure](https://docs.ccxt.com/#/?id=prediction-order-structure)
1346
+ */
1347
+ async fetchOrder(id, outcome = undefined, params = {}) {
1348
+ this.checkRequiredCredentials();
1349
+ if (id === undefined) {
1350
+ throw new ArgumentsRequired(this.id + ' fetchOrder() requires an id argument');
1351
+ }
1352
+ let outcomeObj = undefined;
1353
+ if (outcome !== undefined) {
1354
+ await this.loadOutcome(outcome);
1355
+ outcomeObj = this.outcome(outcome);
1356
+ }
1357
+ const request = { 'orderId': id };
1358
+ const response = await this.sxbetPrivateGetOrdersV3OrderId(this.extend(request, params));
1359
+ const data = this.safeDict(response, 'data', {});
1360
+ const row = this.safeDict(data, 'order', data);
1361
+ return this.parsePredictionOrder(row, outcomeObj);
1362
+ }
1363
+ /**
1364
+ * @method
1365
+ * @name sxbet#fetchTrades
1366
+ * @description fetches the public trade tape of one outcome's market — every bettor's settled and in-flight bets on that market. the venue requires the trades listing to be scoped, so the outcome argument is mandatory
1367
+ * @see https://docs.sx.bet/api-reference/get-trades-v3-public
1368
+ * @param {string} outcome unified outcome or outcomeId
1369
+ * @param {int} [since] timestamp in ms of the earliest trade to return — applied client-side over the newest page (the public tape serves newest-first and has no date filter; older pages are reachable through params.nextKey)
1370
+ * @param {int} [limit] the maximum number of trades to return (server-side perPage, max 100, default 50)
1371
+ * @param {object} [params] extra parameters specific to the exchange API endpoint (e.g. eventId, nextKey)
1372
+ * @returns {object[]} a list of [prediction trade structures](https://docs.ccxt.com/#/?id=prediction-trade-structure)
1373
+ */
1374
+ async fetchTrades(outcome, since = undefined, limit = undefined, params = {}) {
1375
+ if (outcome === undefined) {
1376
+ throw new ArgumentsRequired(this.id + ' fetchTrades() requires an outcome argument - the venue requires the trades listing to be scoped');
1377
+ }
1378
+ await this.loadOutcome(outcome);
1379
+ const outcomeObj = this.outcome(outcome);
1380
+ const request = { 'marketHash': this.safeString(outcomeObj['info'], 'marketHash') };
1381
+ if (limit !== undefined) {
1382
+ request['perPage'] = this.clampSxbetPerPage(limit);
1383
+ }
1384
+ const response = await this.sxbetPublicGetTradesV3Public(this.extend(request, params));
1385
+ const data = this.safeDict(response, 'data', {});
1386
+ const rawTrades = this.safeList(data, 'trades', []);
1387
+ let trades = [];
1388
+ const rawTradesLength = rawTrades.length;
1389
+ for (let i = 0; i < rawTradesLength; i++) {
1390
+ trades.push(this.parseSxbetV3PublicTrade(rawTrades[i]));
1391
+ }
1392
+ // the venue serves the tape newest-first - the unified contract is ascending by timestamp
1393
+ trades = this.sortBy(trades, 'timestamp');
1394
+ const sym = this.safeString(outcomeObj, 'outcome');
1395
+ return this.filterByValueSinceLimit(trades, 'outcome', sym, since, limit, 'timestamp', true);
1396
+ }
1397
+ /**
1398
+ * @method
1399
+ * @name sxbet#fetchMyTrades
1400
+ * @description fetches the account's fills (matched legs of its own orders, both taker and maker side) via the api-key-authenticated GET /fills-v3
1401
+ * @see https://docs.sx.bet/api-reference/get-fills-v3
1402
+ * @param {string} [outcome] unified outcome or outcomeId — narrows to that outcome's market and drops the opposite side's legs
1403
+ * @param {int} [since] timestamp in ms of the earliest fill to fetch (server-side startDate)
1404
+ * @param {int} [limit] the maximum number of fills to return (server-side perPage, max 100, default 50)
1405
+ * @param {object} [params] extra parameters specific to the exchange API endpoint (e.g. tradeId, orderId, endDate, sortAsc, nextKey)
1406
+ * @returns {object[]} a list of [prediction trade structures](https://docs.ccxt.com/#/?id=prediction-trade-structure)
1407
+ */
1408
+ async fetchMyTrades(outcome = undefined, since = undefined, limit = undefined, params = {}) {
1409
+ this.checkRequiredCredentials();
1410
+ const request = {};
1411
+ let outcomeObj = undefined;
1412
+ if (outcome !== undefined) {
1413
+ await this.loadOutcome(outcome);
1414
+ outcomeObj = this.outcome(outcome);
1415
+ }
1416
+ if (since !== undefined) {
1417
+ request['startDate'] = this.iso8601(since);
1418
+ }
1419
+ if ((limit !== undefined) && (outcome === undefined)) {
1420
+ // with an outcome filter the rows are narrowed client-side - a server-side page
1421
+ // size would truncate the page before the filter and under-fill the result
1422
+ request['perPage'] = this.clampSxbetPerPage(limit);
1423
+ }
1424
+ const response = await this.sxbetPrivateGetFillsV3(this.extend(request, params));
1425
+ const data = this.safeDict(response, 'data', {});
1426
+ const rawFills = this.safeList(data, 'fills', []);
1427
+ let trades = [];
1428
+ const rawFillsLength = rawFills.length;
1429
+ for (let i = 0; i < rawFillsLength; i++) {
1430
+ trades.push(this.parseSxbetV3Fill(rawFills[i]));
1431
+ }
1432
+ trades = this.sortBy(trades, 'timestamp');
1433
+ const sym = (outcomeObj !== undefined) ? this.safeString(outcomeObj, 'outcome') : undefined;
1434
+ return this.filterByValueSinceLimit(trades, 'outcome', sym, since, limit, 'timestamp', true);
1435
+ }
1436
+ /**
1437
+ * @ignore
1438
+ * @method
1439
+ * @name sxbet#parseSxbetV3Fill
1440
+ * @description parses one GET /fills-v3 row into a unified trade - each fill is one maker/taker match of the wallet's own order
1441
+ * @param {object} fill the raw fill row
1442
+ * @param {object} [market] the outcome object labelling hint
1443
+ * @returns {object} a [prediction trade structure](https://docs.ccxt.com/#/?id=prediction-trade-structure)
1444
+ */
1445
+ parseSxbetV3Fill(fill, market = undefined) {
1446
+ //
1447
+ // {
1448
+ // "id": "0xec8e...:0x02d2...:0x1fec...",
1449
+ // "matchId": "0xec8e...", "orderId": "0x02d2...", "tradeId": "0xac6b...",
1450
+ // "marketHash": "0x1fec...", "isParlay": false,
1451
+ // "userAddress": "0xbcc6...", "wallet": "0x4361...",
1452
+ // "fillAmount": "333334", "fillOdds": "40000000000000000000",
1453
+ // "returnAmount": "833335", "isBettingOutcomeOne": false,
1454
+ // "isMaker": false, "status": "LOCKED", "txHash": "0x6430...",
1455
+ // "ceRefundAmount": "0", "ceRefundFeeAmount": "0",
1456
+ // "usedBetCredits": false, "createdAt": "2026-07-31T18:47:09.586Z",
1457
+ // "updatedAt": "2026-07-31T18:47:12.837Z", "settlement": null
1458
+ // }
1459
+ //
1460
+ const marketHash = this.safeString(fill, 'marketHash', '');
1461
+ const isBettingOutcomeOne = this.safeBool(fill, 'isBettingOutcomeOne', true);
1462
+ const outcomeId = (isBettingOutcomeOne) ? marketHash : (marketHash + '-2');
1463
+ const outcomeObj = this.safeOutcome(outcomeId, market);
1464
+ const oneDenom = '100000000000000000000';
1465
+ const usdcDecimals = '1000000';
1466
+ const fillOdds = this.safeString(fill, 'fillOdds');
1467
+ const price = (fillOdds !== undefined) ? this.parseNumber(Precise.stringDiv(fillOdds, oneDenom)) : undefined;
1468
+ const fillAmount = this.safeString(fill, 'fillAmount', '0');
1469
+ const amount = this.parseNumber(Precise.stringDiv(fillAmount, usdcDecimals, 6));
1470
+ const timestamp = this.parse8601(this.safeString(fill, 'createdAt'));
1471
+ // isMaker: true when the account's order was the resting one - absent on older rows
1472
+ const isMaker = this.safeBool(fill, 'isMaker');
1473
+ let takerOrMaker = undefined;
1474
+ if (isMaker !== undefined) {
1475
+ takerOrMaker = (isMaker === true) ? 'maker' : 'taker';
1476
+ }
1477
+ return this.safePredictionTrade({
1478
+ 'id': this.safeString(fill, 'id'),
1479
+ 'info': fill,
1480
+ 'timestamp': timestamp,
1481
+ 'datetime': this.iso8601(timestamp),
1482
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
1483
+ 'outcomeId': this.safeString(outcomeObj, 'outcomeId', outcomeId),
1484
+ 'label': this.safeString(outcomeObj, 'label'),
1485
+ 'market': this.safeString(outcomeObj, 'market'),
1486
+ 'order': this.safeString(fill, 'orderId'),
1487
+ 'type': undefined,
1488
+ 'side': 'buy',
1489
+ 'takerOrMaker': takerOrMaker,
1490
+ 'price': price,
1491
+ 'amount': amount,
1492
+ 'cost': amount,
1493
+ 'fee': undefined,
1494
+ }, market);
1495
+ }
1496
+ /**
1497
+ * @method
1498
+ * @name sxbet#fetchBalance
1499
+ * @description fetches the account's order-spendable proxy balance from GET /user/balance-v3 - v3 trading capital sits inside the obv3 proxy wallet (funded via approve()), and GTC posting is checked against availableAmount. free is the spendable availableAmount, used the escrowedAmount locked behind open bets
1500
+ * @see https://docs.sx.bet/api-reference/get-user-balance-v3
1501
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1502
+ * @returns {object} a [balance structure](https://docs.ccxt.com/#/?id=balance-structure)
1503
+ */
1504
+ async fetchBalance(params = {}) {
1505
+ const obv3 = await this.loadSxObv3Metadata();
1506
+ const activeAsset = this.safeDict(obv3, 'activeAsset', {});
1507
+ const usdcAddress = this.safeStringLower(activeAsset, 'baseToken', '');
1508
+ const response = await this.sxbetPrivateGetUserBalanceV3(params);
1509
+ //
1510
+ // { "status": "success", "data": { "balances": [ {
1511
+ // "userAddress": "0xC3f4...", "wallet": "0x19D1...",
1512
+ // "tokenAddress": "0x1BC6...", "escrowAddress": "0x0Db3...",
1513
+ // "availableAmount": "0", "pendingAvailableAmount": "0",
1514
+ // "escrowedAmount": "0", "pendingEscrowAmount": "0" } ] } }
1515
+ //
1516
+ const data = this.safeDict(response, 'data', {});
1517
+ const balances = this.safeList(data, 'balances', []);
1518
+ const result = { 'info': response };
1519
+ const usdcDecimals = '1000000';
1520
+ const balancesLength = balances.length;
1521
+ for (let i = 0; i < balancesLength; i++) {
1522
+ const row = balances[i];
1523
+ const tokenAddress = this.safeStringLower(row, 'tokenAddress', '');
1524
+ // every sxbet market is denominated in the active base token, surfaced under 'USDC';
1525
+ // rows of any other token keep their contract address for the code
1526
+ let code = this.safeString(row, 'tokenAddress', '');
1527
+ if (tokenAddress === usdcAddress) {
1528
+ code = 'USDC';
1529
+ }
1530
+ const free = Precise.stringDiv(this.safeString(row, 'availableAmount', '0'), usdcDecimals, 6);
1531
+ const used = Precise.stringDiv(this.safeString(row, 'escrowedAmount', '0'), usdcDecimals, 6);
1532
+ result[code] = {
1533
+ 'free': this.parseNumber(free),
1534
+ 'used': this.parseNumber(used),
1535
+ 'total': this.parseNumber(Precise.stringAdd(free, used)),
1536
+ };
1537
+ }
1538
+ return this.safeBalance(result);
1539
+ }
1540
+ /**
1541
+ * @method
1542
+ * @name sxbet#fetchPositions
1543
+ * @description fetches the account's open positions from the venue's per-market aggregates (GET /positions-v3, MATCHED and LOCKED bets by default; override with params.status - the enum is MATCHED, LOCKED, SETTLED, FAILED, and pnl is populated only when the filter is exclusively SETTLED). contracts is the total stake at risk, entryPrice the blended implied probability of the market's best-case outcome
1544
+ * @see https://docs.sx.bet/api-reference/get-positions-v3
1545
+ * @param {string[]} [outcomes] filter by unified outcomes or outcomeIds
1546
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1547
+ * @returns {object[]} a list of [prediction position structures](https://docs.ccxt.com/#/?id=prediction-position-structure)
1548
+ */
1549
+ async fetchPositions(outcomes = undefined, params = {}) {
1550
+ this.checkRequiredCredentials();
1551
+ // copy to a plain list so the transpilers and the strict null checks see one shape
1552
+ const outcomesList = (outcomes === undefined) ? [] : outcomes;
1553
+ const outcomesLength = outcomesList.length;
1554
+ const wantedMarkets = {};
1555
+ if (outcomesLength > 0) {
1556
+ await this.loadOutcomes(outcomesList);
1557
+ for (let i = 0; i < outcomesLength; i++) {
1558
+ const outcomeObj = this.outcome(outcomesList[i]);
1559
+ const hash = this.safeString(outcomeObj['info'], 'marketHash', '');
1560
+ wantedMarkets[hash] = true;
1561
+ }
1562
+ }
1563
+ // open exposure lives in MATCHED and LOCKED bets - the venue requires an explicit status
1564
+ // list drawn from MATCHED, LOCKED, SETTLED, FAILED
1565
+ const request = { 'status': this.safeString(params, 'status', 'MATCHED,LOCKED') };
1566
+ const rest = this.omit(params, ['status']);
1567
+ const response = await this.sxbetPrivateGetPositionsV3(this.extend(request, rest));
1568
+ const data = this.safeDict(response, 'data', {});
1569
+ const rawPositions = this.safeList(data, 'positions', []);
1570
+ const result = [];
1571
+ const rawPositionsLength = rawPositions.length;
1572
+ for (let i = 0; i < rawPositionsLength; i++) {
1573
+ const raw = rawPositions[i];
1574
+ const marketHash = this.safeString(raw, 'marketHash', '');
1575
+ if (outcomesLength > 0) {
1576
+ if (this.safeBool(wantedMarkets, marketHash) === undefined) {
1577
+ continue;
1578
+ }
1579
+ }
1580
+ result.push(this.parseSxbetV3Position(raw));
1581
+ }
1582
+ return result;
1583
+ }
1584
+ /**
1585
+ * @ignore
1586
+ * @method
1587
+ * @name sxbet#parseSxbetV3Position
1588
+ * @description parses one GET /positions-v3 row (per-market aggregate) into a unified position on the market's best-case outcome
1589
+ * @param {object} raw the raw position row
1590
+ * @returns {object} a [prediction position structure](https://docs.ccxt.com/#/?id=prediction-position-structure)
1591
+ */
1592
+ parseSxbetV3Position(raw) {
1593
+ //
1594
+ // {
1595
+ // "marketHash": "0x8120...", "market": {...},
1596
+ // "totalStake": "300000000", "maxWin": "200000000", "maxLoss": "-100000000",
1597
+ // "isOutcomeOneMaxWin": true,
1598
+ // "odds": {"outcomeOne": "40000000000000000000", "outcomeTwo": "50000000000000000000"},
1599
+ // "pnl": null, "tradeCount": 2,
1600
+ // "betTime": "2026-03-13T21:14:02.118Z", "updatedAt": "2026-03-13T22:03:41.502Z"
1601
+ // }
1602
+ //
1603
+ const marketHash = this.safeString(raw, 'marketHash', '');
1604
+ const isOutcomeOneMaxWin = this.safeBool(raw, 'isOutcomeOneMaxWin', true);
1605
+ const outcomeId = (isOutcomeOneMaxWin) ? marketHash : (marketHash + '-2');
1606
+ const outcomeObj = this.safeOutcome(outcomeId);
1607
+ const oneDenom = '100000000000000000000';
1608
+ const usdcDecimals = '1000000';
1609
+ const odds = this.safeDict(raw, 'odds', {});
1610
+ const ownOdds = (isOutcomeOneMaxWin) ? this.safeString(odds, 'outcomeOne') : this.safeString(odds, 'outcomeTwo');
1611
+ const entryPrice = (ownOdds !== undefined) ? this.parseNumber(Precise.stringDiv(ownOdds, oneDenom)) : undefined;
1612
+ const totalStake = this.safeString(raw, 'totalStake', '0');
1613
+ const pnl = this.safeString(raw, 'pnl');
1614
+ const timestamp = this.parse8601(this.safeString(raw, 'betTime'));
1615
+ return this.safePredictionPosition({
1616
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
1617
+ 'outcomeId': this.safeString(outcomeObj, 'outcomeId', outcomeId),
1618
+ 'label': this.safeString(outcomeObj, 'label'),
1619
+ 'market': this.safeString(outcomeObj, 'market'),
1620
+ 'timestamp': timestamp,
1621
+ 'datetime': this.iso8601(timestamp),
1622
+ 'lastUpdateTimestamp': this.parse8601(this.safeString(raw, 'updatedAt')),
1623
+ 'contracts': this.parseNumber(Precise.stringDiv(totalStake, usdcDecimals, 6)),
1624
+ 'entryPrice': entryPrice,
1625
+ 'markPrice': undefined,
1626
+ 'unrealizedPnl': undefined,
1627
+ 'realizedPnl': (pnl !== undefined) ? this.parseNumber(Precise.stringDiv(pnl, usdcDecimals, 6)) : undefined,
1628
+ 'side': 'long',
1629
+ 'info': raw,
1630
+ });
1631
+ }
1632
+ /**
1633
+ * @method
1634
+ * @name sxbet#fetchSettlements
1635
+ * @description fetches the account's settled bets — each settled GET /trades-v3 row becomes one settlement with the resolved winner, the payout (stake / odds when won, the stake back when the market voided, zero when lost) and the realized pnl
1636
+ * @see https://docs.sx.bet/api-reference/get-trades-v3
1637
+ * @param {string} [outcome] filter to a single unified outcome or outcomeId
1638
+ * @param {int} [since] timestamp in ms of the earliest settlement to fetch (server-side startDate on the bet time)
1639
+ * @param {int} [limit] the maximum number of settlements to fetch (server-side perPage, max 100, default 50)
1640
+ * @param {object} [params] extra parameters specific to the exchange API endpoint (e.g. eventId, endDate, sortAsc, nextKey)
1641
+ * @returns {object[]} a list of prediction settlement structures
1642
+ */
1643
+ async fetchSettlements(outcome = undefined, since = undefined, limit = undefined, params = {}) {
1644
+ this.checkRequiredCredentials();
1645
+ const request = { 'status': 'SETTLED' };
1646
+ let wantedOutcomeId = undefined;
1647
+ if (outcome !== undefined) {
1648
+ await this.loadOutcome(outcome);
1649
+ const outcomeObj = this.outcome(outcome);
1650
+ request['marketHash'] = this.safeString(outcomeObj['info'], 'marketHash', '');
1651
+ wantedOutcomeId = this.safeString(outcomeObj, 'outcomeId');
1652
+ }
1653
+ if (since !== undefined) {
1654
+ request['startDate'] = this.iso8601(since);
1655
+ }
1656
+ if ((limit !== undefined) && (outcome === undefined)) {
1657
+ // the outcome scope narrows rows client-side (the venue filter is per market, not
1658
+ // per side) - a server-side page size would under-fill the filtered result
1659
+ request['perPage'] = this.clampSxbetPerPage(limit);
1660
+ }
1661
+ const response = await this.sxbetPrivateGetTradesV3(this.extend(request, params));
1662
+ const data = this.safeDict(response, 'data', {});
1663
+ const rawTrades = this.safeList(data, 'trades', []);
1664
+ const result = [];
1665
+ const rawTradesLength = rawTrades.length;
1666
+ for (let i = 0; i < rawTradesLength; i++) {
1667
+ const settlement = this.parseSettlement(rawTrades[i]);
1668
+ if ((wantedOutcomeId === undefined) || (this.safeString(settlement, 'outcomeId') === wantedOutcomeId)) {
1669
+ result.push(settlement);
1670
+ }
1671
+ }
1672
+ return result;
1673
+ }
1674
+ /**
1675
+ * @ignore
1676
+ * @method
1677
+ * @name sxbet#parseSettlement
1678
+ * @description parses one settled GET /trades-v3 row into the unified prediction settlement shape. the raw `outcome` field carries the winner, 1 or 2, with 0 for a voided market, and the full payout of a winning bet is stake / odds (the implied probability the bettor received)
1679
+ * @param {object} trade the raw settled sx.bet trade
1680
+ * @param {object} [market] a resolved outcome/market hint
1681
+ * @returns {object} a prediction settlement structure
1682
+ */
1683
+ parseSettlement(trade, market = undefined) {
1684
+ //
1685
+ // TradeV3 row with a settlement sub-object:
1686
+ // {
1687
+ // "tradeId": "0xac6b...", "marketHash": "0x1fec...",
1688
+ // "isBettingOutcomeOne": false, "totalStake": "1000000", "totalReturn": "2500000",
1689
+ // "weightedAverageOdds": "40000000000000000000", "status": "SETTLED",
1690
+ // "betTime": "2026-07-31T18:47:09.577Z",
1691
+ // "settlement": { "outcome": 2, "settleReturnAmount": "2500000",
1692
+ // "settleFeeAmount": "0", "settleCeRefundAmount": "0",
1693
+ // "settleDate": "2026-08-01T00:10:00.000Z" }
1694
+ // }
1695
+ //
1696
+ const marketHash = this.safeString(trade, 'marketHash', '');
1697
+ const isBettingOutcomeOne = this.safeBool(trade, 'isBettingOutcomeOne', true);
1698
+ const outcomeId = (isBettingOutcomeOne) ? marketHash : (marketHash + '-2');
1699
+ const outcomeObj = this.safeOutcome(outcomeId, market);
1700
+ const settlement = this.safeDict(trade, 'settlement', {});
1701
+ const winner = this.safeInteger(settlement, 'outcome');
1702
+ const isVoid = (winner === 0);
1703
+ const heldNumber = (isBettingOutcomeOne) ? 1 : 2;
1704
+ let won = undefined;
1705
+ if ((winner !== undefined) && !isVoid) {
1706
+ won = (winner === heldNumber);
1707
+ }
1708
+ const usdcDecimals = '1000000';
1709
+ const stake = Precise.stringDiv(this.safeString(trade, 'totalStake', '0'), usdcDecimals, 6);
1710
+ const payout = Precise.stringDiv(this.safeString(settlement, 'settleReturnAmount', '0'), usdcDecimals, 6);
1711
+ const pnl = Precise.stringSub(payout, stake);
1712
+ // resolve the winning side's human label through the cached market when available
1713
+ let resultLabel = undefined;
1714
+ if (isVoid) {
1715
+ resultLabel = 'VOID';
1716
+ }
1717
+ else if (winner !== undefined) {
1718
+ const info = this.safeDict(outcomeObj, 'info', {});
1719
+ const labelKey = (winner === 1) ? 'outcomeOneName' : 'outcomeTwoName';
1720
+ resultLabel = this.safeString(info, labelKey, this.numberToString(winner));
1721
+ }
1722
+ const timestamp = this.parse8601(this.safeString(settlement, 'settleDate'));
1723
+ let settlePrice = undefined;
1724
+ if (won !== undefined) {
1725
+ settlePrice = (won === true) ? 1 : 0;
1726
+ }
1727
+ return {
1728
+ 'info': trade,
1729
+ 'id': this.safeString(trade, 'tradeId'),
1730
+ 'timestamp': timestamp,
1731
+ 'datetime': this.iso8601(timestamp),
1732
+ 'outcome': this.safeString(outcomeObj, 'outcome', outcomeId),
1733
+ 'outcomeId': this.safeString(outcomeObj, 'outcomeId', outcomeId),
1734
+ 'market': this.safeString(outcomeObj, 'market'),
1735
+ 'event': undefined,
1736
+ 'result': resultLabel,
1737
+ 'won': won,
1738
+ 'amount': this.parseNumber(stake),
1739
+ 'price': settlePrice,
1740
+ 'cost': this.parseNumber(stake),
1741
+ 'payout': this.parseNumber(payout),
1742
+ 'pnl': this.parseNumber(pnl),
1743
+ };
1744
+ }
1745
+ /**
1746
+ * @ignore
1747
+ * @method
1748
+ * @name sxbet#fetchSxbetBookSnapshot
1749
+ * @description fetches the v3 aggregated order book snapshot of one market ({outcomeOne, outcomeTwo, version} price levels)
1750
+ * @see https://docs.sx.bet/api-reference/get-orderbook-snapshot
1751
+ * @param {string} marketHash the market hash
1752
+ * @returns {object} the raw snapshot data
1753
+ */
1754
+ async fetchSxbetBookSnapshot(marketHash) {
1755
+ const request = { 'marketHash': marketHash };
1756
+ const response = await this.sxbetPublicGetOrderbookV3Snapshot(request);
1757
+ return this.safeDict(response, 'data', {});
1758
+ }
1759
+ /**
1760
+ * @method
1761
+ * @name sxbet#fetchTicker
1762
+ * @description fetches the current best resting odds for a single sx.bet outcome. sx.bet is a peer-to-peer odds book (no matched-trade tape or candles), so bid/ask are the best (highest) percentageOdds resting on this outcome's own side and its mirror (1 - best percentageOdds resting on the opposite outcome)
1763
+ * @see https://docs.sx.bet/api-reference/get-orderbook-snapshot
1764
+ * @param {string} outcome unified outcome handle or outcomeId (marketHash or marketHash + '-2')
1765
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1766
+ * @returns {object} a [prediction ticker structure](https://docs.ccxt.com/#/?id=prediction-ticker-structure)
1767
+ */
1768
+ async fetchTicker(outcome, params = {}) {
1769
+ await this.loadOutcome(outcome);
1770
+ const outcomeObj = this.outcome(outcome);
1771
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash');
1772
+ // the book snapshot is public and carries the same top of book - the batched best-odds
1773
+ // route needs an apiKey, so it only pays off for the multi-market path
1774
+ const snapshot = await this.fetchSxbetBookSnapshot(marketHash);
1775
+ const raw = this.parseSxbetSnapshotBestOdds(snapshot);
1776
+ return this.parsePredictionTicker(raw, outcomeObj);
1777
+ }
1778
+ /**
1779
+ * @ignore
1780
+ * @method
1781
+ * @name sxbet#fetchSxbetBestOdds
1782
+ * @description fetches the best resting level of both sides for a set of markets in one request
1783
+ * @see https://docs.sx.bet/api-reference/get-best-odds-v3
1784
+ * @param {string[]} marketHashes the market hashes to query, at most 100 per call
1785
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1786
+ * @returns {object[]} the raw bestOdds rows ({marketHash, outcomeOne, outcomeTwo})
1787
+ */
1788
+ async fetchSxbetBestOdds(marketHashes, params = {}) {
1789
+ const request = { 'marketHashes': marketHashes.join(',') };
1790
+ const response = await this.sxbetPrivateGetOrdersV3OddsBest(this.extend(request, params));
1791
+ const data = this.safeDict(response, 'data', {});
1792
+ return this.safeList(data, 'bestOdds', []);
1793
+ }
1794
+ /**
1795
+ * @ignore
1796
+ * @method
1797
+ * @name sxbet#parseSxbetSnapshotBestOdds
1798
+ * @description reduces a v3 book snapshot to the legacy best-odds shape the ticker parser consumes ({marketHash, outcomeOne: {percentageOdds}, outcomeTwo: {percentageOdds}})
1799
+ * @param {object} snapshot the raw snapshot data
1800
+ * @returns {object} the best-odds shaped dict
1801
+ */
1802
+ parseSxbetSnapshotBestOdds(snapshot) {
1803
+ // levels arrive sorted best-first; the best level's odds mirror the v2 best-odds fields
1804
+ const oneLevels = this.safeList(snapshot, 'outcomeOne', []);
1805
+ const twoLevels = this.safeList(snapshot, 'outcomeTwo', []);
1806
+ const bestOne = this.safeDict(oneLevels, 0, {});
1807
+ const bestTwo = this.safeDict(twoLevels, 0, {});
1808
+ return {
1809
+ 'marketHash': this.safeString(snapshot, 'marketHash'),
1810
+ 'outcomeOne': { 'percentageOdds': this.safeString(bestOne, 'percentageOdds') },
1811
+ 'outcomeTwo': { 'percentageOdds': this.safeString(bestTwo, 'percentageOdds') },
1812
+ };
1813
+ }
1814
+ /**
1815
+ * @method
1816
+ * @name sxbet#fetchTickers
1817
+ * @description fetches the current best resting odds for multiple sx.bet outcomes, one book snapshot per market
1818
+ * @see https://docs.sx.bet/api-reference/get-orderbook-snapshot
1819
+ * @param {string[]} outcomes unified outcomes - required: sx.bet has thousands of markets and no endpoint returning all of them at once
1820
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1821
+ * @returns {object} a dictionary of [prediction ticker structures](https://docs.ccxt.com/#/?id=prediction-ticker-structure) indexed by outcome
1822
+ */
1823
+ async fetchTickers(outcomes = undefined, params = {}) {
1824
+ if (outcomes === undefined) {
1825
+ throw new ArgumentsRequired(this.id + ' fetchTickers() requires an outcomes argument - sx.bet has thousands of markets and serves best odds per market list');
1826
+ }
1827
+ const outcomesList = outcomes;
1828
+ const outcomesLength = outcomesList.length;
1829
+ const hashesOrder = [];
1830
+ const seenHashes = {};
1831
+ for (let i = 0; i < outcomesLength; i++) {
1832
+ await this.loadOutcome(outcomesList[i]);
1833
+ const outcomeObj = this.outcome(outcomesList[i]);
1834
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash', '');
1835
+ if (this.safeBool(seenHashes, marketHash) === undefined) {
1836
+ seenHashes[marketHash] = true;
1837
+ hashesOrder.push(marketHash);
1838
+ }
1839
+ }
1840
+ // both outcomes of a market share one row, and the venue takes at most 100 hashes per call.
1841
+ // the batched route is apiKey-authenticated, so fall back to the public per-market
1842
+ // snapshots when the instance carries no credentials
1843
+ const rowsByHash = {};
1844
+ const hashesLength = hashesOrder.length;
1845
+ const hasApiKey = !this.isEmptyString(this.apiKey);
1846
+ if (!hasApiKey) {
1847
+ for (let i = 0; i < hashesLength; i++) {
1848
+ const marketHash = hashesOrder[i];
1849
+ const snapshot = await this.fetchSxbetBookSnapshot(marketHash);
1850
+ rowsByHash[marketHash] = this.parseSxbetSnapshotBestOdds(snapshot);
1851
+ }
1852
+ return this.parseSxbetTickersByHash(outcomesList, rowsByHash);
1853
+ }
1854
+ const chunkSize = this.safeInteger(this.options, 'bestOddsBatchSize', 100);
1855
+ const chunkCount = this.parseToInt(this.sum(hashesLength, chunkSize - 1) / chunkSize);
1856
+ for (let c = 0; c < chunkCount; c++) {
1857
+ const start = c * chunkSize;
1858
+ let end = start + chunkSize;
1859
+ if (end > hashesLength) {
1860
+ end = hashesLength;
1861
+ }
1862
+ const chunk = this.arraySlice(hashesOrder, start, end);
1863
+ const rows = await this.fetchSxbetBestOdds(chunk, params);
1864
+ const rowsLength = rows.length;
1865
+ for (let j = 0; j < rowsLength; j++) {
1866
+ const row = rows[j];
1867
+ const rowHash = this.safeString(row, 'marketHash');
1868
+ if (rowHash !== undefined) {
1869
+ rowsByHash[rowHash] = row;
1870
+ }
1871
+ }
1872
+ }
1873
+ return this.parseSxbetTickersByHash(outcomesList, rowsByHash);
1874
+ }
1875
+ /**
1876
+ * @ignore
1877
+ * @method
1878
+ * @name sxbet#parseSxbetTickersByHash
1879
+ * @description assembles the unified tickers dict from best-odds shaped rows keyed by market hash
1880
+ * @param {string[]} outcomesList the requested unified outcomes
1881
+ * @param {object} rowsByHash best-odds rows indexed by market hash
1882
+ * @returns {object} a dictionary of [prediction ticker structures](https://docs.ccxt.com/#/?id=prediction-ticker-structure) indexed by outcome
1883
+ */
1884
+ parseSxbetTickersByHash(outcomesList, rowsByHash) {
1885
+ const result = {};
1886
+ const outcomesLength = outcomesList.length;
1887
+ for (let i = 0; i < outcomesLength; i++) {
1888
+ const outcomeObj = this.outcome(outcomesList[i]);
1889
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash', '');
1890
+ const raw = this.safeDict(rowsByHash, marketHash);
1891
+ if (raw === undefined) {
1892
+ continue;
1893
+ }
1894
+ const ticker = this.parsePredictionTicker(raw, outcomeObj);
1895
+ const sym = this.safeString(ticker, 'outcome');
1896
+ if (sym !== undefined) {
1897
+ result[sym] = ticker;
1898
+ }
1899
+ }
1900
+ return result;
1901
+ }
1902
+ /**
1903
+ * @ignore
1904
+ * @method
1905
+ * @name sxbet#parsePredictionTicker
1906
+ * @description parses one /orders/odds/best entry into a unified ticker for one side of the market
1907
+ * @param {object} raw one bestOdds entry ({ marketHash, baseToken, outcomeOne: { percentageOdds, updatedAt }, outcomeTwo: {...} })
1908
+ * @param {object} [market] the outcome object the ticker belongs to
1909
+ * @returns {object} a [prediction ticker structure](https://docs.ccxt.com/#/?id=prediction-ticker-structure)
1910
+ */
1911
+ parsePredictionTicker(raw, market = undefined) {
1912
+ //
1913
+ // {
1914
+ // "marketHash": "0x48ce0ef287c59fb3bb3ca6deb80de0d5791eb761ea8e16d6f03c8e93ef28d1de",
1915
+ // "baseToken": "0x6629Ce1Cf35Cc1329ebB4F63202F3f197b3F050B",
1916
+ // "outcomeOne": { "percentageOdds": "50250000000000000000", "updatedAt": 1785697831141 },
1917
+ // "outcomeTwo": { "percentageOdds": "48000000000000000000", "updatedAt": 1785699649869 }
1918
+ // }
1919
+ //
1920
+ const marketAny = market;
1921
+ const outcomeObj = this.safeOutcome(this.safeString(marketAny, 'outcome'), marketAny);
1922
+ const outcomeId = this.safeString(outcomeObj, 'outcomeId');
1923
+ const marketHash = this.safeString(raw, 'marketHash');
1924
+ const isOutcomeOne = (outcomeId === marketHash);
1925
+ const outcomeOneOdds = this.safeDict(raw, 'outcomeOne', {});
1926
+ const outcomeTwoOdds = this.safeDict(raw, 'outcomeTwo', {});
1927
+ const ownOdds = (isOutcomeOne) ? outcomeOneOdds : outcomeTwoOdds;
1928
+ const oppositeOdds = (isOutcomeOne) ? outcomeTwoOdds : outcomeOneOdds;
1929
+ // percentageOdds is the maker's own implied probability * 1e20 (sx.bet protocol format);
1930
+ // the opposite side's best resting maker mirrors into this outcome's ask via 1 - p
1931
+ const oneDenom = '100000000000000000000';
1932
+ const ownPercentage = this.safeString(ownOdds, 'percentageOdds');
1933
+ const oppositePercentage = this.safeString(oppositeOdds, 'percentageOdds');
1934
+ const bid = (ownPercentage !== undefined) ? this.parseNumber(Precise.stringDiv(ownPercentage, oneDenom)) : undefined;
1935
+ const ask = (oppositePercentage !== undefined) ? this.parseNumber(Precise.stringSub('1', Precise.stringDiv(oppositePercentage, oneDenom))) : undefined;
1936
+ const updatedAt = this.safeInteger(ownOdds, 'updatedAt');
1937
+ const now = this.milliseconds();
1938
+ const timestamp = (updatedAt !== undefined) ? updatedAt : now;
1939
+ let average = undefined;
1940
+ if ((bid !== undefined) && (ask !== undefined)) {
1941
+ average = this.parseNumber(Precise.stringDiv(Precise.stringAdd(this.numberToString(bid), this.numberToString(ask)), '2'));
1942
+ }
1943
+ return this.safePredictionTicker({
1944
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
1945
+ 'outcomeId': outcomeId,
1946
+ 'label': this.safeString(outcomeObj, 'label'),
1947
+ 'market': this.safeString(outcomeObj, 'market'),
1948
+ 'timestamp': timestamp,
1949
+ 'datetime': this.iso8601(timestamp),
1950
+ 'high': undefined,
1951
+ 'low': undefined,
1952
+ 'bid': bid,
1953
+ 'bidVolume': undefined,
1954
+ 'ask': ask,
1955
+ 'askVolume': undefined,
1956
+ 'open': undefined,
1957
+ 'close': bid,
1958
+ 'last': bid,
1959
+ 'change': undefined,
1960
+ 'percentage': undefined,
1961
+ 'average': average,
1962
+ 'baseVolume': undefined,
1963
+ 'quoteVolume': undefined,
1964
+ 'info': raw,
1965
+ }, market);
1966
+ }
1967
+ /**
1968
+ * @method
1969
+ * @name sxbet#fetchOrderBook
1970
+ * @description fetches the resting maker order book for a single sx.bet outcome. bids are maker orders already betting on this outcome (priced at each maker's own implied probability, sized by their remaining stake); asks mirror the opposite outcome's maker orders (price = 1 - their implied probability, sized by how much a taker could bet against them, per sx.bet's remaining-taker-space formula) — the same YES/NO-style mirrored construction used across this codebase's other binary prediction venues
1971
+ * @see https://docs.sx.bet/api-reference/get-orderbook-snapshot
1972
+ * @param {string} outcome unified outcome handle or outcomeId
1973
+ * @param {int} [limit] the maximum number of bids/asks to return
1974
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1975
+ * @returns {object} a [prediction order book structure](https://docs.ccxt.com/#/?id=prediction-order-book-structure)
1976
+ */
1977
+ async fetchOrderBook(outcome, limit = undefined, params = {}) {
1978
+ await this.loadOutcome(outcome);
1979
+ const outcomeObj = this.outcome(outcome);
1980
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash');
1981
+ const outcomeId = this.safeString(outcomeObj, 'outcomeId');
1982
+ const isOutcomeOne = (outcomeId === marketHash);
1983
+ const request = { 'marketHash': marketHash };
1984
+ const response = await this.sxbetPublicGetOrderbookV3Snapshot(this.extend(request, params));
1985
+ const snapshot = this.safeDict(response, 'data', {});
1986
+ const sides = this.parseSxbetV3BookSides(snapshot, isOutcomeOne);
1987
+ let sortedBids = this.safeList(sides, 'bids', []);
1988
+ let sortedAsks = this.safeList(sides, 'asks', []);
1989
+ if (limit !== undefined) {
1990
+ const bidsLength = sortedBids.length;
1991
+ let bidsEnd = limit;
1992
+ if (bidsEnd > bidsLength) {
1993
+ bidsEnd = bidsLength;
1994
+ }
1995
+ sortedBids = this.arraySlice(sortedBids, 0, bidsEnd);
1996
+ const asksLength = sortedAsks.length;
1997
+ let asksEnd = limit;
1998
+ if (asksEnd > asksLength) {
1999
+ asksEnd = asksLength;
2000
+ }
2001
+ sortedAsks = this.arraySlice(sortedAsks, 0, asksEnd);
2002
+ }
2003
+ const timestamp = this.milliseconds();
2004
+ return this.safePredictionOrderBook({
2005
+ 'outcome': this.safeString(outcomeObj, 'outcome', outcome),
2006
+ 'bids': sortedBids,
2007
+ 'asks': sortedAsks,
2008
+ 'timestamp': timestamp,
2009
+ 'datetime': this.iso8601(timestamp),
2010
+ 'nonce': undefined,
2011
+ }, outcomeObj);
2012
+ }
2013
+ /**
2014
+ * @ignore
2015
+ * @method
2016
+ * @name sxbet#parseSxbetV3BookSides
2017
+ * @description converts a v3 aggregated book snapshot into sorted [price, amount] bid/ask levels for one outcome; shared by fetchOrderBook and the websocket book handler
2018
+ * @param {object} snapshot the raw snapshot ({outcomeOne, outcomeTwo} level lists of {percentageOdds, size})
2019
+ * @param {boolean} isOutcomeOne whether the book is built for the market's outcome one
2020
+ * @returns {object} a dict with sorted 'bids' and 'asks' lists
2021
+ */
2022
+ parseSxbetV3BookSides(snapshot, isOutcomeOne) {
2023
+ const oneDenom = '100000000000000000000';
2024
+ const usdcDecimals = '1000000';
2025
+ const ownLevels = (isOutcomeOne) ? this.safeList(snapshot, 'outcomeOne', []) : this.safeList(snapshot, 'outcomeTwo', []);
2026
+ const oppositeLevels = (isOutcomeOne) ? this.safeList(snapshot, 'outcomeTwo', []) : this.safeList(snapshot, 'outcomeOne', []);
2027
+ const bids = [];
2028
+ const ownLevelsLength = ownLevels.length;
2029
+ for (let i = 0; i < ownLevelsLength; i++) {
2030
+ const level = ownLevels[i];
2031
+ const percentageOdds = this.safeString(level, 'percentageOdds');
2032
+ const size = this.safeString(level, 'size', '0');
2033
+ const price = this.parseNumber(Precise.stringDiv(percentageOdds, oneDenom));
2034
+ const amount = this.parseNumber(Precise.stringDiv(size, usdcDecimals, 6));
2035
+ bids.push([price, amount]);
2036
+ }
2037
+ const asks = [];
2038
+ const oppositeLevelsLength = oppositeLevels.length;
2039
+ for (let i = 0; i < oppositeLevelsLength; i++) {
2040
+ const level = oppositeLevels[i];
2041
+ const percentageOdds = this.safeString(level, 'percentageOdds');
2042
+ const size = this.safeString(level, 'size', '0');
2043
+ // the opposite side's resting stake mirrors into this outcome's ask - the price is the
2044
+ // complement, and the takeable amount follows the remaining-taker-space formula
2045
+ const price = this.parseNumber(Precise.stringSub('1', Precise.stringDiv(percentageOdds, oneDenom)));
2046
+ const ratio = Precise.stringDiv(oneDenom, percentageOdds, 12);
2047
+ const remainingTaker = Precise.stringSub(Precise.stringMul(size, ratio), size);
2048
+ const amount = this.parseNumber(Precise.stringDiv(remainingTaker, usdcDecimals, 6));
2049
+ asks.push([price, amount]);
2050
+ }
2051
+ return {
2052
+ 'bids': this.sortBy(bids, 0, true),
2053
+ 'asks': this.sortBy(asks, 0),
2054
+ };
2055
+ }
2056
+ requestId(url) {
2057
+ const existing = this.safeValue(this.options, 'requestId');
2058
+ if (existing === undefined) {
2059
+ this.options['requestId'] = this.createSafeDictionary();
2060
+ }
2061
+ const options = this.options['requestId'];
2062
+ const previousValue = this.safeInteger(options, url, 0);
2063
+ const newValue = this.sum(previousValue, 1);
2064
+ if (url !== undefined) {
2065
+ this.options['requestId'][url] = newValue;
2066
+ }
2067
+ return newValue;
2068
+ }
2069
+ /**
2070
+ * @ignore
2071
+ * @method
2072
+ * @name sxbet#registerSxbetWsRequest
2073
+ * @description tracks an outgoing Centrifugo connect/subscribe command by its request id, so an error reply can be correlated back to the awaiting future and its registered subscription hash (see handleCentrifugoFrame)
2074
+ * @param {int} requestId the id sent with the command
2075
+ * @param {string} messageHash the future hash the caller awaits
2076
+ * @param {string} subscription the subscription hash registered by watch() ('connect' or the channel name)
2077
+ */
2078
+ registerSxbetWsRequest(requestId, messageHash, subscription) {
2079
+ const existing = this.safeDict(this.options, 'wsPendingRequests');
2080
+ if (existing === undefined) {
2081
+ this.options['wsPendingRequests'] = this.createSafeDictionary();
2082
+ }
2083
+ const requestIdString = this.numberToString(requestId);
2084
+ this.options['wsPendingRequests'][requestIdString] = { 'messageHash': messageHash, 'subscription': subscription };
2085
+ }
2086
+ /**
2087
+ * @ignore
2088
+ * @method
2089
+ * @name sxbet#fetchSxbetRealtimeToken
2090
+ * @description fetches the short-lived Centrifugo connection token from the relayer; requires the apiKey credential (X-Api-Key header)
2091
+ * @see https://docs.sx.bet/developers/realtime-initialization
2092
+ * @returns {string} the JWT connection token
2093
+ */
2094
+ async fetchSxbetRealtimeToken() {
2095
+ if (this.apiKey === undefined) {
2096
+ throw new ArgumentsRequired(this.id + ' websocket streaming requires the apiKey credential - the realtime token endpoint authenticates with the X-Api-Key header');
2097
+ }
2098
+ const response = await this.sxbetPrivateGetUserRealtimeTokenV3ApiKey();
2099
+ const data = this.safeDict(response, 'data', {});
2100
+ return this.safeString2(data, 'token', 'realtimeToken', this.safeString(response, 'token'));
2101
+ }
2102
+ async connectSxbetCentrifugo(url) {
2103
+ // Centrifugo requires a token-carrying connect command before any subscribe. This sends it once
2104
+ // per connection and resolves when the connect reply arrives (see handleCentrifugoFrame). The base
2105
+ // clears client.subscriptions on reconnect, so an absent 'connect' marker means a fresh handshake.
2106
+ const client = this.client(url);
2107
+ const connectSent = this.safeValue(client.subscriptions, 'connect');
2108
+ if (connectSent === undefined) {
2109
+ this.options['wsConnected'] = false;
2110
+ const token = await this.fetchSxbetRealtimeToken();
2111
+ const requestId = this.requestId(url);
2112
+ this.registerSxbetWsRequest(requestId, 'centrifugoConnected', 'connect');
2113
+ const connectMsg = { 'connect': { 'token': token, 'name': 'ccxt' }, 'id': requestId };
2114
+ return await this.watch(url, 'centrifugoConnected', connectMsg, 'connect');
2115
+ }
2116
+ if (this.safeBool(this.options, 'wsConnected', false)) {
2117
+ // the connect reply already arrived on the current connection - safe to subscribe immediately
2118
+ return undefined;
2119
+ }
2120
+ // connect is in flight, sent by a concurrent subscribe - wait on the shared reply future
2121
+ return await client.future('centrifugoConnected');
2122
+ }
2123
+ async pong(client, message = undefined) {
2124
+ // Centrifugo server pings are empty frames; reply with the same empty frame to keep the link alive
2125
+ await client.send('{}');
2126
+ }
2127
+ async subscribeSxbetChannel(messageHash, channel) {
2128
+ const url = this.safeString(this.urls['api'], 'ws');
2129
+ // finish the connect handshake first so the subscribe frame follows the connect reply
2130
+ await this.connectSxbetCentrifugo(url);
2131
+ const requestId = this.requestId(url);
2132
+ this.registerSxbetWsRequest(requestId, messageHash, channel);
2133
+ const subscribeMsg = { 'subscribe': { 'channel': channel }, 'id': requestId };
2134
+ return await this.watch(url, messageHash, subscribeMsg, channel);
2135
+ }
2136
+ handleMessage(client, message) {
2137
+ // Centrifugo packs several commands per frame joined by newlines; a multi-command frame fails the
2138
+ // base JSON.parse and arrives here in raw-string form, a single command arrives already parsed
2139
+ if (typeof message === 'string') {
2140
+ const lines = message.split('\n');
2141
+ const linesLength = lines.length;
2142
+ for (let i = 0; i < linesLength; i++) {
2143
+ const line = lines[i];
2144
+ if (line.length > 0) {
2145
+ const parsed = JSON.parse(line);
2146
+ this.handleCentrifugoFrame(client, parsed);
2147
+ }
2148
+ }
2149
+ return;
2150
+ }
2151
+ this.handleCentrifugoFrame(client, message);
2152
+ }
2153
+ handleCentrifugoFrame(client, msg) {
2154
+ const keys = Object.keys(msg);
2155
+ const keysLength = keys.length;
2156
+ if (keysLength === 0) {
2157
+ this.spawn(this.pong, client, msg);
2158
+ return;
2159
+ }
2160
+ const requestIdString = this.safeString(msg, 'id');
2161
+ const pendingRequests = this.safeDict(this.options, 'wsPendingRequests', {});
2162
+ const pendingEntry = this.safeDict(pendingRequests, requestIdString);
2163
+ const errorReply = this.safeDict(msg, 'error');
2164
+ if (errorReply !== undefined) {
2165
+ // a rejected connect (bad or expired realtime token) or subscribe (unauthorized or
2166
+ // unknown channel) - fail the awaiting future and clear the subscription hash that
2167
+ // watch() registered at send time, so the next call re-sends the command instead
2168
+ // of waiting forever behind a marker that will never be served
2169
+ const error = new ExchangeError(this.id + ' ' + this.json(msg));
2170
+ if (pendingEntry !== undefined) {
2171
+ const failedHash = this.safeString(pendingEntry, 'messageHash');
2172
+ const subscription = this.safeString(pendingEntry, 'subscription');
2173
+ if (subscription === 'connect') {
2174
+ this.options['wsConnected'] = false;
2175
+ }
2176
+ if ((subscription !== undefined) && (subscription in client.subscriptions)) {
2177
+ delete client.subscriptions[subscription];
2178
+ }
2179
+ this.options['wsPendingRequests'] = this.omit(pendingRequests, requestIdString);
2180
+ client.reject(error, failedHash);
2181
+ }
2182
+ else {
2183
+ // an error that cannot be correlated to one command fails the whole connection
2184
+ client.reject(error);
2185
+ }
2186
+ return;
2187
+ }
2188
+ if (pendingEntry !== undefined) {
2189
+ // a successful command ack - the tracked request has served its purpose
2190
+ this.options['wsPendingRequests'] = this.omit(pendingRequests, requestIdString);
2191
+ }
2192
+ const connectReply = this.safeDict(msg, 'connect');
2193
+ if (connectReply !== undefined) {
2194
+ // connect acknowledged - unblock connectSxbetCentrifugo so channel subscribes can be sent
2195
+ this.options['wsConnected'] = true;
2196
+ client.resolve(true, 'centrifugoConnected');
2197
+ return;
2198
+ }
2199
+ const push = this.safeDict(msg, 'push');
2200
+ if (push === undefined) {
2201
+ return;
2202
+ }
2203
+ const channel = this.safeString(push, 'channel');
2204
+ if (channel === undefined) {
2205
+ return;
2206
+ }
2207
+ const pub = this.safeDict(push, 'pub', {});
2208
+ const data = this.safeValue(pub, 'data');
2209
+ if (data === undefined) {
2210
+ return;
2211
+ }
2212
+ // rows arrive either shaped like one object or like an array of objects - normalize to a list
2213
+ let rows = [];
2214
+ if (Array.isArray(data)) {
2215
+ rows = data;
2216
+ }
2217
+ else {
2218
+ rows = [data];
2219
+ }
2220
+ const parts = channel.split(':');
2221
+ const channelType = this.safeString(parts, 0);
2222
+ if (channelType === 'orderbook_v3') {
2223
+ this.handleOrderBook(client, rows);
2224
+ }
2225
+ else if (channelType === 'best_odds_v3') {
2226
+ this.handleTicker(client, rows);
2227
+ }
2228
+ else if (channelType === 'recent_trades_v3') {
2229
+ this.handleTrades(client, rows);
2230
+ }
2231
+ else if (channelType === 'account') {
2232
+ // per-account channels: account:orders_v3_#addr / account:fills_v3_#addr
2233
+ if (channel.indexOf('orders_v3') >= 0) {
2234
+ this.handleOrder(client, rows);
2235
+ }
2236
+ else if (channel.indexOf('fills_v3') >= 0) {
2237
+ this.handleMyTrade(client, rows);
2238
+ }
2239
+ }
2240
+ }
2241
+ /**
2242
+ * @method
2243
+ * @name sxbet#watchOrderBook
2244
+ * @description streams the order book of an outcome - the v3 channel publishes the entire aggregated book on every update with a monotonic version, so each message replaces the held book
2245
+ * @see https://docs.sx.bet/api-reference/channel-orderbook-v3
2246
+ * @param {string} outcome unified outcome or outcome token id
2247
+ * @param {int} [limit] the maximum number of order book entries to return
2248
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
2249
+ * @returns {object} a [prediction order book structure](https://docs.ccxt.com/#/?id=prediction-order-book-structure)
2250
+ */
2251
+ async watchOrderBook(outcome, limit = undefined, params = {}) {
2252
+ await this.loadOutcome(outcome);
2253
+ const outcomeObj = this.outcome(outcome);
2254
+ const sym = this.safeString(outcomeObj, 'outcome');
2255
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash');
2256
+ const channel = 'orderbook_v3:' + marketHash;
2257
+ const messageHash = 'orderbook::' + sym;
2258
+ const url = this.safeString(this.urls['api'], 'ws');
2259
+ await this.connectSxbetCentrifugo(url);
2260
+ const client = this.client(url);
2261
+ const isNewSubscription = this.safeValue(client.subscriptions, channel) === undefined;
2262
+ if (this.safeValue(this.orderbooks, sym) === undefined) {
2263
+ // seed from the REST snapshot so the book is served before the first publication
2264
+ const watchedBooks = this.safeDict(this.options, 'wsWatchedBooks');
2265
+ if (watchedBooks === undefined) {
2266
+ this.options['wsWatchedBooks'] = this.createSafeDictionary();
2267
+ }
2268
+ this.options['wsWatchedBooks'][sym] = marketHash;
2269
+ const snapshot = await this.fetchSxbetBookSnapshot(marketHash);
2270
+ this.applySxbetWsSnapshot(snapshot);
2271
+ if (this.safeValue(this.orderbooks, sym) === undefined) {
2272
+ const emptyBook = this.orderBook({});
2273
+ this.orderbooks[sym] = emptyBook;
2274
+ }
2275
+ }
2276
+ const requestId = this.requestId(url);
2277
+ this.registerSxbetWsRequest(requestId, messageHash, channel);
2278
+ const subscribeMsg = { 'subscribe': { 'channel': channel }, 'id': requestId };
2279
+ const future = this.watch(url, messageHash, subscribeMsg, channel);
2280
+ if (isNewSubscription) {
2281
+ // return the freshly-seeded book immediately instead of blocking until the next publication
2282
+ client.resolve(this.safeValue(this.orderbooks, sym), messageHash);
2283
+ }
2284
+ const orderbook = await future;
2285
+ return orderbook.limit();
2286
+ }
2287
+ /**
2288
+ * @ignore
2289
+ * @method
2290
+ * @name sxbet#applySxbetWsSnapshot
2291
+ * @description applies one full v3 book publication ({marketHash, version, outcomeOne, outcomeTwo}) to every watched outcome of that market, honoring the monotonic version
2292
+ * @param {object} snapshot the raw book publication
2293
+ * @returns {string[]} the outcome handles whose books were refreshed
2294
+ */
2295
+ applySxbetWsSnapshot(snapshot) {
2296
+ const marketHash = this.safeString(snapshot, 'marketHash');
2297
+ const version = this.safeString(snapshot, 'version', '');
2298
+ const refreshed = [];
2299
+ if (marketHash === undefined) {
2300
+ return refreshed;
2301
+ }
2302
+ const versionsAll = this.safeDict(this.options, 'wsBookVersions');
2303
+ if (versionsAll === undefined) {
2304
+ this.options['wsBookVersions'] = this.createSafeDictionary();
2305
+ }
2306
+ const held = this.safeString(this.options['wsBookVersions'], marketHash, '');
2307
+ // versions are fixed-width numeric strings - replace only on a strictly newer publication
2308
+ if ((held !== '') && (version <= held)) {
2309
+ return refreshed;
2310
+ }
2311
+ this.options['wsBookVersions'][marketHash] = version;
2312
+ const watchedBooks = this.safeDict(this.options, 'wsWatchedBooks', {});
2313
+ const watchedSyms = Object.keys(watchedBooks);
2314
+ const timestamp = this.milliseconds();
2315
+ const watchedSymsLength = watchedSyms.length;
2316
+ for (let i = 0; i < watchedSymsLength; i++) {
2317
+ const sym = watchedSyms[i];
2318
+ if (this.safeString(watchedBooks, sym) !== marketHash) {
2319
+ continue;
2320
+ }
2321
+ const outcomeObj = this.outcome(sym);
2322
+ const outcomeId = this.safeString(outcomeObj, 'outcomeId');
2323
+ const isOutcomeOne = (outcomeId === marketHash);
2324
+ const sides = this.parseSxbetV3BookSides(snapshot, isOutcomeOne);
2325
+ let orderbook = this.safeValue(this.orderbooks, sym);
2326
+ if (orderbook === undefined) {
2327
+ orderbook = this.orderBook({});
2328
+ this.orderbooks[sym] = orderbook;
2329
+ }
2330
+ const bookSnapshot = {
2331
+ 'bids': this.safeList(sides, 'bids', []),
2332
+ 'asks': this.safeList(sides, 'asks', []),
2333
+ 'timestamp': timestamp,
2334
+ 'datetime': this.iso8601(timestamp),
2335
+ 'nonce': undefined,
2336
+ };
2337
+ orderbook.reset(bookSnapshot);
2338
+ refreshed.push(sym);
2339
+ }
2340
+ return refreshed;
2341
+ }
2342
+ handleOrderBook(client, rows) {
2343
+ //
2344
+ // {
2345
+ // "marketHash": "0x...",
2346
+ // "version": "00100000000000010015000",
2347
+ // "outcomeOne": [ { "percentageOdds": "40000000000000000000", "size": "1000000" } ],
2348
+ // "outcomeTwo": []
2349
+ // }
2350
+ //
2351
+ const rowsLength = rows.length;
2352
+ for (let i = 0; i < rowsLength; i++) {
2353
+ const refreshed = this.applySxbetWsSnapshot(rows[i]);
2354
+ const refreshedLength = refreshed.length;
2355
+ for (let j = 0; j < refreshedLength; j++) {
2356
+ const sym = refreshed[j];
2357
+ client.resolve(this.safeValue(this.orderbooks, sym), 'orderbook::' + sym);
2358
+ }
2359
+ }
2360
+ }
2361
+ /**
2362
+ * @method
2363
+ * @name sxbet#watchTicker
2364
+ * @description streams best-odds updates of an outcome; the venue channel is global, entries are filtered down to the requested outcome's market
2365
+ * @see https://docs.sx.bet/api-reference/channel-best-odds-v3
2366
+ * @param {string} outcome unified outcome or outcome token id
2367
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
2368
+ * @returns {object} a [prediction ticker structure](https://docs.ccxt.com/#/?id=prediction-ticker-structure)
2369
+ */
2370
+ async watchTicker(outcome, params = {}) {
2371
+ await this.loadOutcome(outcome);
2372
+ const outcomeObj = this.outcome(outcome);
2373
+ const sym = this.safeString(outcomeObj, 'outcome');
2374
+ const marketHash = this.safeString(outcomeObj['info'], 'marketHash');
2375
+ const messageHash = 'ticker::' + sym;
2376
+ const watchedTickers = this.safeDict(this.options, 'wsWatchedTickers');
2377
+ if (watchedTickers === undefined) {
2378
+ this.options['wsWatchedTickers'] = this.createSafeDictionary();
2379
+ }
2380
+ this.options['wsWatchedTickers'][sym] = marketHash;
2381
+ const url = this.safeString(this.urls['api'], 'ws');
2382
+ await this.connectSxbetCentrifugo(url);
2383
+ const client = this.client(url);
2384
+ const channel = 'best_odds_v3:global';
2385
+ let hydrated = false;
2386
+ if (this.safeValue(this.tickers, sym) === undefined) {
2387
+ // hydrate from the REST snapshot so the first call does not hang until the
2388
+ // market's next top-of-book change - the channel is global, so the seed must fire
2389
+ // for every newly watched market, not only on a fresh subscription
2390
+ const snapshot = await this.fetchSxbetBookSnapshot(marketHash);
2391
+ const raw = this.parseSxbetSnapshotBestOdds(snapshot);
2392
+ const ticker = this.parsePredictionTicker(raw, outcomeObj);
2393
+ this.tickers[sym] = ticker;
2394
+ hydrated = true;
2395
+ }
2396
+ const requestId = this.requestId(url);
2397
+ this.registerSxbetWsRequest(requestId, messageHash, channel);
2398
+ const subscribeMsg = { 'subscribe': { 'channel': channel }, 'id': requestId };
2399
+ const future = this.watch(url, messageHash, subscribeMsg, channel);
2400
+ if (hydrated) {
2401
+ client.resolve(this.safeValue(this.tickers, sym), messageHash);
2402
+ }
2403
+ const tickerResult = await future;
2404
+ return tickerResult;
2405
+ }
2406
+ handleTicker(client, rows) {
2407
+ //
2408
+ // {
2409
+ // "marketHash": "0xbf06...16a2eb",
2410
+ // "outcomeOne": { "percentageOdds": "40000000000000000000", "size": "1000000" },
2411
+ // "outcomeTwo": null
2412
+ // }
2413
+ //
2414
+ const watchedTickers = this.safeDict(this.options, 'wsWatchedTickers', {});
2415
+ const watchedSyms = Object.keys(watchedTickers);
2416
+ const rowsLength = rows.length;
2417
+ for (let i = 0; i < rowsLength; i++) {
2418
+ const entry = rows[i];
2419
+ const marketHash = this.safeString(entry, 'marketHash');
2420
+ if (marketHash === undefined) {
2421
+ continue;
2422
+ }
2423
+ // every publication carries BOTH sides (null = no resting liquidity), so the legacy
2424
+ // best-odds shape rebuilds directly without any merge cache
2425
+ const bestOne = this.safeDict(entry, 'outcomeOne', {});
2426
+ const bestTwo = this.safeDict(entry, 'outcomeTwo', {});
2427
+ const raw = {
2428
+ 'marketHash': marketHash,
2429
+ 'outcomeOne': { 'percentageOdds': this.safeString(bestOne, 'percentageOdds') },
2430
+ 'outcomeTwo': { 'percentageOdds': this.safeString(bestTwo, 'percentageOdds') },
2431
+ };
2432
+ const watchedSymsLength = watchedSyms.length;
2433
+ for (let j = 0; j < watchedSymsLength; j++) {
2434
+ const sym = watchedSyms[j];
2435
+ if (this.safeString(watchedTickers, sym) !== marketHash) {
2436
+ continue;
2437
+ }
2438
+ const outcomeObj = this.outcome(sym);
2439
+ const ticker = this.parsePredictionTicker(raw, outcomeObj);
2440
+ this.tickers[sym] = ticker;
2441
+ client.resolve(ticker, 'ticker::' + sym);
2442
+ }
2443
+ }
2444
+ }
2445
+ /**
2446
+ * @method
2447
+ * @name sxbet#watchTrades
2448
+ * @description streams public bets of an outcome; the venue channel is global, entries are filtered down to the requested outcome
2449
+ * @see https://docs.sx.bet/api-reference/channel-recent-trades-v3
2450
+ * @param {string} outcome unified outcome or outcome token id
2451
+ * @param {int} [since] timestamp in ms of the earliest trade to return
2452
+ * @param {int} [limit] the maximum number of trades to return
2453
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
2454
+ * @returns {object[]} a list of [prediction trade structures](https://docs.ccxt.com/#/?id=prediction-trade-structure)
2455
+ */
2456
+ async watchTrades(outcome, since = undefined, limit = undefined, params = {}) {
2457
+ await this.loadOutcome(outcome);
2458
+ const outcomeObj = this.outcome(outcome);
2459
+ const sym = this.safeString(outcomeObj, 'outcome');
2460
+ const messageHash = 'trades::' + sym;
2461
+ const trades = await this.subscribeSxbetChannel(messageHash, 'recent_trades_v3:global');
2462
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
2463
+ }
2464
+ /**
2465
+ * @ignore
2466
+ * @method
2467
+ * @name sxbet#parseSxbetV3PublicTrade
2468
+ * @description parses one recent_trades_v3 publication (public TradeV3 shape) into a unified trade
2469
+ * @param {object} trade the raw public trade row
2470
+ * @returns {object} a [prediction trade structure](https://docs.ccxt.com/#/?id=prediction-trade-structure)
2471
+ */
2472
+ parseSxbetV3PublicTrade(trade) {
2473
+ const marketHash = this.safeString(trade, 'marketHash', '');
2474
+ const isBettingOutcomeOne = this.safeBool(trade, 'isBettingOutcomeOne', true);
2475
+ const outcomeId = (isBettingOutcomeOne) ? marketHash : (marketHash + '-2');
2476
+ const outcomeObj = this.safeOutcome(outcomeId);
2477
+ const oneDenom = '100000000000000000000';
2478
+ const usdcDecimals = '1000000';
2479
+ const odds = this.safeString(trade, 'weightedAverageOdds');
2480
+ const price = (odds !== undefined) ? this.parseNumber(Precise.stringDiv(odds, oneDenom)) : undefined;
2481
+ const stake = this.safeString(trade, 'totalStake', '0');
2482
+ const amount = this.parseNumber(Precise.stringDiv(stake, usdcDecimals, 6));
2483
+ const timestamp = this.parse8601(this.safeString(trade, 'betTime'));
2484
+ return this.safePredictionTrade({
2485
+ 'id': this.safeString(trade, 'tradeId'),
2486
+ 'info': trade,
2487
+ 'timestamp': timestamp,
2488
+ 'datetime': this.iso8601(timestamp),
2489
+ 'outcome': this.safeString(outcomeObj, 'outcome'),
2490
+ 'outcomeId': this.safeString(outcomeObj, 'outcomeId', outcomeId),
2491
+ 'label': this.safeString(outcomeObj, 'label'),
2492
+ 'market': this.safeString(outcomeObj, 'market'),
2493
+ 'order': undefined,
2494
+ 'type': undefined,
2495
+ 'side': 'buy',
2496
+ 'takerOrMaker': undefined,
2497
+ 'price': price,
2498
+ 'amount': amount,
2499
+ 'cost': amount,
2500
+ 'fee': undefined,
2501
+ });
2502
+ }
2503
+ handleTrades(client, rows) {
2504
+ //
2505
+ // { "trade": { "tradeId": "0xac6b...", "marketHash": "0x1fec...", "isBettingOutcomeOne": true,
2506
+ // "totalStake": "1000000", "weightedAverageOdds": "40000000000000000000",
2507
+ // "betTime": "2026-07-31T18:22:41.000Z", ... } }
2508
+ //
2509
+ const rowsLength = rows.length;
2510
+ for (let i = 0; i < rowsLength; i++) {
2511
+ const row = this.safeDict(rows[i], 'trade', rows[i]);
2512
+ const trade = this.parseSxbetV3PublicTrade(row);
2513
+ const sym = this.safeString(trade, 'outcome');
2514
+ if (sym === undefined) {
2515
+ // bet in a market absent from the loaded outcome cache
2516
+ continue;
2517
+ }
2518
+ if (this.trades === undefined) {
2519
+ this.trades = this.createSafeDictionary();
2520
+ }
2521
+ if (this.safeValue(this.trades, sym) === undefined) {
2522
+ const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
2523
+ this.trades[sym] = new ArrayCache(tradesLimit);
2524
+ }
2525
+ const stored = this.trades[sym];
2526
+ stored.append(trade);
2527
+ client.resolve(stored, 'trades::' + sym);
2528
+ }
2529
+ }
2530
+ /**
2531
+ * @method
2532
+ * @name sxbet#watchMyTrades
2533
+ * @description streams the authenticated wallet's fills over its per-account v3 channel
2534
+ * @see https://docs.sx.bet/api-reference/channel-fills-v3
2535
+ * @param {string} [outcome] unified outcome or outcome token id to narrow the stream down to
2536
+ * @param {int} [since] timestamp in ms of the earliest trade to return
2537
+ * @param {int} [limit] the maximum number of trades to return
2538
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
2539
+ * @returns {object[]} a list of [prediction trade structures](https://docs.ccxt.com/#/?id=prediction-trade-structure)
2540
+ */
2541
+ async watchMyTrades(outcome = undefined, since = undefined, limit = undefined, params = {}) {
2542
+ if (this.walletAddress === undefined) {
2543
+ throw new ArgumentsRequired(this.id + ' watchMyTrades() requires a walletAddress');
2544
+ }
2545
+ let messageHash = 'myTrades';
2546
+ if (outcome !== undefined) {
2547
+ await this.loadOutcome(outcome);
2548
+ const outcomeObj = this.outcome(outcome);
2549
+ const sym = this.safeString(outcomeObj, 'outcome');
2550
+ messageHash = 'myTrades::' + sym;
2551
+ }
2552
+ const channel = 'account:fills_v3_#' + this.walletAddress;
2553
+ const trades = await this.subscribeSxbetChannel(messageHash, channel);
2554
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
2555
+ }
2556
+ handleMyTrade(client, rows) {
2557
+ //
2558
+ // FillV3 rows - identical to GET /fills-v3
2559
+ //
2560
+ const rowsLength = rows.length;
2561
+ for (let i = 0; i < rowsLength; i++) {
2562
+ const row = this.safeDict(rows[i], 'fill', rows[i]);
2563
+ const trade = this.parseSxbetV3Fill(row);
2564
+ const sym = this.safeString(trade, 'outcome');
2565
+ if (sym === undefined) {
2566
+ continue;
2567
+ }
2568
+ if (this.myTrades === undefined) {
2569
+ const myTradesLimit = this.safeInteger(this.options, 'myTradesLimit', 1000);
2570
+ this.myTrades = new ArrayCacheByOutcomeById(myTradesLimit);
2571
+ }
2572
+ const stored = this.myTrades;
2573
+ stored.append(trade);
2574
+ client.resolve(stored, 'myTrades');
2575
+ client.resolve(stored, 'myTrades::' + sym);
2576
+ }
2577
+ }
2578
+ /**
2579
+ * @method
2580
+ * @name sxbet#watchOrders
2581
+ * @description streams updates of the authenticated wallet's orders over its per-account v3 channel
2582
+ * @see https://docs.sx.bet/api-reference/channel-orders-v3
2583
+ * @param {string} [outcome] unified outcome or outcome token id to narrow the stream down to
2584
+ * @param {int} [since] timestamp in ms of the earliest order to return
2585
+ * @param {int} [limit] the maximum number of orders to return
2586
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
2587
+ * @returns {object[]} a list of [prediction order structures](https://docs.ccxt.com/#/?id=prediction-order-structure)
2588
+ */
2589
+ async watchOrders(outcome = undefined, since = undefined, limit = undefined, params = {}) {
2590
+ if (this.walletAddress === undefined) {
2591
+ throw new ArgumentsRequired(this.id + ' watchOrders() requires a walletAddress');
2592
+ }
2593
+ let messageHash = 'orders';
2594
+ if (outcome !== undefined) {
2595
+ await this.loadOutcome(outcome);
2596
+ const outcomeObj = this.outcome(outcome);
2597
+ const sym = this.safeString(outcomeObj, 'outcome');
2598
+ messageHash = 'orders::' + sym;
2599
+ }
2600
+ const channel = 'account:orders_v3_#' + this.walletAddress;
2601
+ const orders = await this.subscribeSxbetChannel(messageHash, channel);
2602
+ return this.filterBySinceLimit(orders, since, limit, 'timestamp', true);
2603
+ }
2604
+ handleOrder(client, rows) {
2605
+ //
2606
+ // OrderV3 rows - identical to GET /orders-v3 (plus INACTIVE states with inactiveReason)
2607
+ //
2608
+ const rowsLength = rows.length;
2609
+ for (let i = 0; i < rowsLength; i++) {
2610
+ const row = this.safeDict(rows[i], 'order', rows[i]);
2611
+ const order = this.parsePredictionOrder(row);
2612
+ if (this.orders === undefined) {
2613
+ const cacheLimit = this.safeInteger(this.options, 'ordersLimit', 1000);
2614
+ this.orders = new ArrayCacheByOutcomeById(cacheLimit);
2615
+ }
2616
+ const stored = this.orders;
2617
+ stored.append(order);
2618
+ client.resolve(stored, 'orders');
2619
+ const sym = this.safeString(order, 'outcome');
2620
+ if (sym !== undefined) {
2621
+ client.resolve(stored, 'orders::' + sym);
2622
+ }
2623
+ }
2624
+ }
2625
+ handleErrors(code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
2626
+ // sx.bet returns { "error": "Bad Request", "message": <code or [strings]>, "statusCode": 4xx };
2627
+ // map the known codes to ccxt errors so callers can distinguish a rejected order or a
2628
+ // validation problem from a transport outage (the base otherwise maps a bare 4xx to the
2629
+ // exchange-not-available error). unmapped codes fall through to the base http-status handling
2630
+ if (response === undefined) {
2631
+ return undefined;
2632
+ }
2633
+ const message = this.safeValue(response, 'message');
2634
+ if (message === undefined) {
2635
+ return undefined;
2636
+ }
2637
+ // validation failures arrive in string-array form, business errors in single-code-string form
2638
+ let messageString = undefined;
2639
+ if (Array.isArray(message)) {
2640
+ messageString = message.join(', ');
2641
+ }
2642
+ else {
2643
+ messageString = message;
2644
+ }
2645
+ const feedback = this.id + ' ' + body;
2646
+ this.throwExactlyMatchedException(this.exceptions['exact'], messageString, feedback);
2647
+ this.throwBroadlyMatchedException(this.exceptions['broad'], messageString, feedback);
2648
+ // a 400 is a client-side bad request (bad params, invalid order), not a transport outage —
2649
+ // throw BadRequest instead of letting the base map the bare 400 to a retryable network-unavailable error
2650
+ if (code === 400) {
2651
+ throw new BadRequest(feedback);
2652
+ }
2653
+ return undefined;
2654
+ }
2655
+ /**
2656
+ * @ignore
2657
+ * @method
2658
+ * @name sxbet#sign
2659
+ * @description builds the request url and attaches the x-sx-api-key header; every private v3 route authenticates with the apiKey credential, so its absence fails fast instead of surfacing a raw 401
2660
+ * @param {string} path the endpoint path
2661
+ * @param {string|string[]} api the api group and access level
2662
+ * @param {string} method the http method
2663
+ * @param {object} params the request parameters
2664
+ * @param {object} [headers] request headers
2665
+ * @param {string} [body] the request body
2666
+ * @returns {object} a dict with url, method, body and headers
2667
+ */
2668
+ sign(path, api = 'sxbet', method = 'GET', params = {}, headers = undefined, body = undefined) {
2669
+ const apiGroup = typeof api === 'string' ? api : api[0];
2670
+ const accessLevel = typeof api === 'string' ? 'public' : api[1];
2671
+ if ((accessLevel === 'private') && (this.apiKey === undefined)) {
2672
+ throw new AuthenticationError(this.id + ' ' + path + ' is a private endpoint and requires the apiKey credential (the x-sx-api-key header)');
2673
+ }
2674
+ const baseUrls = this.urls['api'];
2675
+ const baseUrl = this.safeString(baseUrls, apiGroup, baseUrls['sxbet']);
2676
+ let url = baseUrl + '/' + this.implodeParams(path, params);
2677
+ const query = this.omit(params, this.extractParams(path));
2678
+ const existingHeaders = (headers !== undefined) ? headers : {};
2679
+ headers = this.extend({
2680
+ 'Accept': 'application/json',
2681
+ 'Content-Type': 'application/json',
2682
+ }, existingHeaders);
2683
+ if (this.apiKey !== undefined) {
2684
+ headers['x-sx-api-key'] = this.apiKey;
2685
+ }
2686
+ // DELETE /orders-v3 carries its order ids in a JSON body; the other DELETE routes -
2687
+ // /orders-v3/all and /orders-v3/event - take query parameters, like every GET
2688
+ let sendAsQuery = (method === 'GET');
2689
+ if (method === 'DELETE') {
2690
+ const hasOrdersList = ('orders' in query);
2691
+ sendAsQuery = !hasOrdersList;
2692
+ }
2693
+ if (sendAsQuery) {
2694
+ const querystring = this.urlencode(query);
2695
+ if (querystring !== '') {
2696
+ url += '?' + querystring;
2697
+ }
2698
+ }
2699
+ else {
2700
+ const queryKeys = Object.keys(query);
2701
+ const queryKeysLength = queryKeys.length;
2702
+ if (queryKeysLength > 0) {
2703
+ body = this.json(query);
2704
+ }
2705
+ }
2706
+ return { 'url': url, 'method': method, 'body': body, 'headers': headers };
2707
+ }
2708
+ }