@mysten/deepbook-v3 2.0.1 → 2.1.4

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 (276) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/PREDICT.md +256 -0
  3. package/README.md +116 -0
  4. package/dist/account.d.mts +139 -0
  5. package/dist/account.d.mts.map +1 -0
  6. package/dist/account.mjs +181 -0
  7. package/dist/account.mjs.map +1 -0
  8. package/dist/bcs/integers.mjs +23 -0
  9. package/dist/bcs/integers.mjs.map +1 -0
  10. package/dist/client.d.mts +2 -2
  11. package/dist/contracts/account/account.d.mts +382 -0
  12. package/dist/contracts/account/account.d.mts.map +1 -0
  13. package/dist/contracts/account/account.mjs +443 -0
  14. package/dist/contracts/account/account.mjs.map +1 -0
  15. package/dist/contracts/account/account_events.d.mts +41 -0
  16. package/dist/contracts/account/account_events.d.mts.map +1 -0
  17. package/dist/contracts/account/account_events.mjs +72 -0
  18. package/dist/contracts/account/account_events.mjs.map +1 -0
  19. package/dist/contracts/account/account_registry.d.mts +210 -0
  20. package/dist/contracts/account/account_registry.d.mts.map +1 -0
  21. package/dist/contracts/account/account_registry.mjs +263 -0
  22. package/dist/contracts/account/account_registry.mjs.map +1 -0
  23. package/dist/contracts/account/config-arguments.d.mts +10 -0
  24. package/dist/contracts/account/config-arguments.d.mts.map +1 -0
  25. package/dist/contracts/account/deps/sui/bag.mjs +44 -0
  26. package/dist/contracts/account/deps/sui/bag.mjs.map +1 -0
  27. package/dist/contracts/deepbook/account.d.mts +18 -18
  28. package/dist/contracts/deepbook/balances.d.mts +4 -4
  29. package/dist/contracts/deepbook/deep_price.d.mts +3 -3
  30. package/dist/contracts/deepbook/deep_price.d.mts.map +1 -1
  31. package/dist/contracts/deepbook/order.d.mts +12 -12
  32. package/dist/contracts/deepbook_predict/builder_code_events.mjs +38 -0
  33. package/dist/contracts/deepbook_predict/builder_code_events.mjs.map +1 -0
  34. package/dist/contracts/deepbook_predict/config-arguments.d.mts +13 -0
  35. package/dist/contracts/deepbook_predict/config-arguments.d.mts.map +1 -0
  36. package/dist/contracts/deepbook_predict/deps/fixed_math/i64.mjs +25 -0
  37. package/dist/contracts/deepbook_predict/deps/fixed_math/i64.mjs.map +1 -0
  38. package/dist/contracts/deepbook_predict/deps/sui/balance.mjs +25 -0
  39. package/dist/contracts/deepbook_predict/deps/sui/balance.mjs.map +1 -0
  40. package/dist/contracts/deepbook_predict/deps/sui/coin.mjs +24 -0
  41. package/dist/contracts/deepbook_predict/deps/sui/coin.mjs.map +1 -0
  42. package/dist/contracts/deepbook_predict/deps/sui/table.mjs +39 -0
  43. package/dist/contracts/deepbook_predict/deps/sui/table.mjs.map +1 -0
  44. package/dist/contracts/deepbook_predict/deps/sui/vec_set.mjs +26 -0
  45. package/dist/contracts/deepbook_predict/deps/sui/vec_set.mjs.map +1 -0
  46. package/dist/contracts/deepbook_predict/ewma.mjs +29 -0
  47. package/dist/contracts/deepbook_predict/ewma.mjs.map +1 -0
  48. package/dist/contracts/deepbook_predict/expiry_cash.mjs +27 -0
  49. package/dist/contracts/deepbook_predict/expiry_cash.mjs.map +1 -0
  50. package/dist/contracts/deepbook_predict/expiry_market.mjs +367 -0
  51. package/dist/contracts/deepbook_predict/expiry_market.mjs.map +1 -0
  52. package/dist/contracts/deepbook_predict/lp_book.mjs +92 -0
  53. package/dist/contracts/deepbook_predict/lp_book.mjs.map +1 -0
  54. package/dist/contracts/deepbook_predict/market_manager.mjs +59 -0
  55. package/dist/contracts/deepbook_predict/market_manager.mjs.map +1 -0
  56. package/dist/contracts/deepbook_predict/order.mjs +26 -0
  57. package/dist/contracts/deepbook_predict/order.mjs.map +1 -0
  58. package/dist/contracts/deepbook_predict/order_events.mjs +84 -0
  59. package/dist/contracts/deepbook_predict/order_events.mjs.map +1 -0
  60. package/dist/contracts/deepbook_predict/plp.mjs +284 -0
  61. package/dist/contracts/deepbook_predict/plp.mjs.map +1 -0
  62. package/dist/contracts/deepbook_predict/pool_accounting.mjs +58 -0
  63. package/dist/contracts/deepbook_predict/pool_accounting.mjs.map +1 -0
  64. package/dist/contracts/deepbook_predict/predict_account.mjs +106 -0
  65. package/dist/contracts/deepbook_predict/predict_account.mjs.map +1 -0
  66. package/dist/contracts/deepbook_predict/pricing.mjs +80 -0
  67. package/dist/contracts/deepbook_predict/pricing.mjs.map +1 -0
  68. package/dist/contracts/deepbook_predict/range_codec.mjs +41 -0
  69. package/dist/contracts/deepbook_predict/range_codec.mjs.map +1 -0
  70. package/dist/contracts/deepbook_predict/registry.mjs +55 -0
  71. package/dist/contracts/deepbook_predict/registry.mjs.map +1 -0
  72. package/dist/contracts/deepbook_predict/strike_exposure.mjs +65 -0
  73. package/dist/contracts/deepbook_predict/strike_exposure.mjs.map +1 -0
  74. package/dist/contracts/deepbook_predict/strike_exposure_config.mjs +33 -0
  75. package/dist/contracts/deepbook_predict/strike_exposure_config.mjs.map +1 -0
  76. package/dist/contracts/deepbook_predict/strike_payout_tree.mjs +72 -0
  77. package/dist/contracts/deepbook_predict/strike_payout_tree.mjs.map +1 -0
  78. package/dist/contracts/deepbook_predict/vault_events.mjs +184 -0
  79. package/dist/contracts/deepbook_predict/vault_events.mjs.map +1 -0
  80. package/dist/contracts/deepbook_sessions/config-arguments.d.mts +10 -0
  81. package/dist/contracts/deepbook_sessions/config-arguments.d.mts.map +1 -0
  82. package/dist/contracts/deepbook_sessions/deps/sui/vec_map.mjs +37 -0
  83. package/dist/contracts/deepbook_sessions/deps/sui/vec_map.mjs.map +1 -0
  84. package/dist/contracts/deepbook_sessions/session_config.d.mts +63 -0
  85. package/dist/contracts/deepbook_sessions/session_config.d.mts.map +1 -0
  86. package/dist/contracts/deepbook_sessions/session_config.mjs +82 -0
  87. package/dist/contracts/deepbook_sessions/session_config.mjs.map +1 -0
  88. package/dist/contracts/deepbook_sessions/sessions.d.mts +275 -0
  89. package/dist/contracts/deepbook_sessions/sessions.d.mts.map +1 -0
  90. package/dist/contracts/deepbook_sessions/sessions.mjs +443 -0
  91. package/dist/contracts/deepbook_sessions/sessions.mjs.map +1 -0
  92. package/dist/contracts/utils/index.d.mts +18 -1
  93. package/dist/contracts/utils/index.d.mts.map +1 -1
  94. package/dist/deployments/index.d.mts +31 -0
  95. package/dist/deployments/index.d.mts.map +1 -0
  96. package/dist/deployments/index.mjs +30 -0
  97. package/dist/deployments/index.mjs.map +1 -0
  98. package/dist/deployments/testnet.d.mts +14 -0
  99. package/dist/deployments/testnet.d.mts.map +1 -0
  100. package/dist/deployments/testnet.mjs +73 -0
  101. package/dist/deployments/testnet.mjs.map +1 -0
  102. package/dist/deployments/types.d.mts +36 -0
  103. package/dist/deployments/types.d.mts.map +1 -0
  104. package/dist/index.mjs +0 -1
  105. package/dist/predict/client.d.mts +255 -0
  106. package/dist/predict/client.d.mts.map +1 -0
  107. package/dist/predict/client.mjs +436 -0
  108. package/dist/predict/client.mjs.map +1 -0
  109. package/dist/predict/config/generated.d.mts +29 -0
  110. package/dist/predict/config/generated.d.mts.map +1 -0
  111. package/dist/predict/config/generated.mjs +16 -0
  112. package/dist/predict/config/generated.mjs.map +1 -0
  113. package/dist/predict/config/index.d.mts +10 -0
  114. package/dist/predict/config/index.d.mts.map +1 -0
  115. package/dist/predict/config/index.mjs +13 -0
  116. package/dist/predict/config/index.mjs.map +1 -0
  117. package/dist/predict/config/testnet.d.mts +17 -0
  118. package/dist/predict/config/testnet.d.mts.map +1 -0
  119. package/dist/predict/config/testnet.mjs +26 -0
  120. package/dist/predict/config/testnet.mjs.map +1 -0
  121. package/dist/predict/config/types.d.mts +55 -0
  122. package/dist/predict/config/types.d.mts.map +1 -0
  123. package/dist/predict/decode.d.mts +162 -0
  124. package/dist/predict/decode.d.mts.map +1 -0
  125. package/dist/predict/decode.mjs +175 -0
  126. package/dist/predict/decode.mjs.map +1 -0
  127. package/dist/predict/errors.d.mts +42 -0
  128. package/dist/predict/errors.d.mts.map +1 -0
  129. package/dist/predict/errors.mjs +37 -0
  130. package/dist/predict/errors.mjs.map +1 -0
  131. package/dist/predict/index.d.mts +18 -0
  132. package/dist/predict/index.mjs +14 -0
  133. package/dist/predict/pricing.d.mts +73 -0
  134. package/dist/predict/pricing.d.mts.map +1 -0
  135. package/dist/predict/pricing.mjs +129 -0
  136. package/dist/predict/pricing.mjs.map +1 -0
  137. package/dist/predict/reads/balances.mjs +41 -0
  138. package/dist/predict/reads/balances.mjs.map +1 -0
  139. package/dist/predict/reads/inspect.d.mts +8 -0
  140. package/dist/predict/reads/inspect.d.mts.map +1 -0
  141. package/dist/predict/reads/inspect.mjs +37 -0
  142. package/dist/predict/reads/inspect.mjs.map +1 -0
  143. package/dist/predict/reads/markets.mjs +130 -0
  144. package/dist/predict/reads/markets.mjs.map +1 -0
  145. package/dist/predict/reads/parse.mjs +20 -0
  146. package/dist/predict/reads/parse.mjs.map +1 -0
  147. package/dist/predict/reads/pool.mjs +26 -0
  148. package/dist/predict/reads/pool.mjs.map +1 -0
  149. package/dist/predict/reads/positions.d.mts +12 -0
  150. package/dist/predict/reads/positions.d.mts.map +1 -0
  151. package/dist/predict/reads/positions.mjs +77 -0
  152. package/dist/predict/reads/positions.mjs.map +1 -0
  153. package/dist/predict/reads/pricing.d.mts +20 -0
  154. package/dist/predict/reads/pricing.d.mts.map +1 -0
  155. package/dist/predict/reads/pricing.mjs +43 -0
  156. package/dist/predict/reads/pricing.mjs.map +1 -0
  157. package/dist/predict/ticks.d.mts +11 -0
  158. package/dist/predict/ticks.d.mts.map +1 -0
  159. package/dist/predict/ticks.mjs +21 -0
  160. package/dist/predict/ticks.mjs.map +1 -0
  161. package/dist/predict/tx/authed.mjs +18 -0
  162. package/dist/predict/tx/authed.mjs.map +1 -0
  163. package/dist/predict/tx/common.d.mts +18 -0
  164. package/dist/predict/tx/common.d.mts.map +1 -0
  165. package/dist/predict/tx/common.mjs +52 -0
  166. package/dist/predict/tx/common.mjs.map +1 -0
  167. package/dist/predict/tx/trade.d.mts +15 -0
  168. package/dist/predict/tx/trade.d.mts.map +1 -0
  169. package/dist/predict/tx/trade.mjs +86 -0
  170. package/dist/predict/tx/trade.mjs.map +1 -0
  171. package/dist/predict/units.d.mts +11 -0
  172. package/dist/predict/units.d.mts.map +1 -0
  173. package/dist/predict/units.mjs +44 -0
  174. package/dist/predict/units.mjs.map +1 -0
  175. package/dist/queries/accountQueries.mjs +0 -1
  176. package/dist/queries/accountQueries.mjs.map +1 -1
  177. package/dist/queries/orderQueries.mjs +0 -1
  178. package/dist/queries/orderQueries.mjs.map +1 -1
  179. package/dist/queries/registryQueries.mjs +0 -1
  180. package/dist/queries/registryQueries.mjs.map +1 -1
  181. package/dist/sessions.d.mts +244 -0
  182. package/dist/sessions.d.mts.map +1 -0
  183. package/dist/sessions.mjs +291 -0
  184. package/dist/sessions.mjs.map +1 -0
  185. package/dist/transactions/balanceManager.d.mts +12 -12
  186. package/dist/transactions/balanceManager.d.mts.map +1 -1
  187. package/dist/transactions/deepbook.d.mts +20 -20
  188. package/dist/transactions/deepbook.d.mts.map +1 -1
  189. package/dist/transactions/deepbookAdmin.d.mts +4 -4
  190. package/dist/transactions/deepbookAdmin.d.mts.map +1 -1
  191. package/dist/transactions/marginAdmin.d.mts +7 -7
  192. package/dist/transactions/marginLiquidations.d.mts +3 -3
  193. package/dist/transactions/marginMaintainer.d.mts +5 -5
  194. package/dist/transactions/marginManager.d.mts +32 -32
  195. package/dist/transactions/marginPool.d.mts +18 -18
  196. package/dist/transactions/marginRegistry.d.mts +16 -16
  197. package/dist/transactions/marginTPSL.d.mts +10 -10
  198. package/dist/transactions/marginTPSL.d.mts.map +1 -1
  199. package/dist/transactions/poolProxy.d.mts +8 -8
  200. package/dist/transactions/poolProxy.d.mts.map +1 -1
  201. package/package.json +25 -4
  202. package/src/account.ts +239 -0
  203. package/src/bcs/integers.ts +35 -0
  204. package/src/contracts/account/account.ts +706 -0
  205. package/src/contracts/account/account_events.ts +64 -0
  206. package/src/contracts/account/account_registry.ts +468 -0
  207. package/src/contracts/account/config-arguments.ts +8 -0
  208. package/src/contracts/account/deps/sui/bag.ts +42 -0
  209. package/src/contracts/deepbook_predict/admin.ts +44 -0
  210. package/src/contracts/deepbook_predict/builder_code.ts +155 -0
  211. package/src/contracts/deepbook_predict/builder_code_events.ts +34 -0
  212. package/src/contracts/deepbook_predict/config-arguments.ts +11 -0
  213. package/src/contracts/deepbook_predict/config_events.ts +140 -0
  214. package/src/contracts/deepbook_predict/deps/fixed_math/i64.ts +21 -0
  215. package/src/contracts/deepbook_predict/deps/sui/balance.ts +25 -0
  216. package/src/contracts/deepbook_predict/deps/sui/coin.ts +20 -0
  217. package/src/contracts/deepbook_predict/deps/sui/table.ts +37 -0
  218. package/src/contracts/deepbook_predict/deps/sui/vec_set.ts +22 -0
  219. package/src/contracts/deepbook_predict/ewma.ts +26 -0
  220. package/src/contracts/deepbook_predict/ewma_config.ts +28 -0
  221. package/src/contracts/deepbook_predict/expiry_cash.ts +24 -0
  222. package/src/contracts/deepbook_predict/expiry_market.ts +1549 -0
  223. package/src/contracts/deepbook_predict/lp_book.ts +105 -0
  224. package/src/contracts/deepbook_predict/market_lifecycle_cap.ts +75 -0
  225. package/src/contracts/deepbook_predict/market_manager.ts +218 -0
  226. package/src/contracts/deepbook_predict/order.ts +24 -0
  227. package/src/contracts/deepbook_predict/order_events.ts +129 -0
  228. package/src/contracts/deepbook_predict/pause_cap.ts +68 -0
  229. package/src/contracts/deepbook_predict/plp.ts +987 -0
  230. package/src/contracts/deepbook_predict/pool_accounting.ts +79 -0
  231. package/src/contracts/deepbook_predict/predict_account.ts +180 -0
  232. package/src/contracts/deepbook_predict/pricing.ts +124 -0
  233. package/src/contracts/deepbook_predict/pricing_config.ts +41 -0
  234. package/src/contracts/deepbook_predict/protocol_config.ts +1066 -0
  235. package/src/contracts/deepbook_predict/range_codec.ts +53 -0
  236. package/src/contracts/deepbook_predict/registry.ts +694 -0
  237. package/src/contracts/deepbook_predict/strike_exposure.ts +77 -0
  238. package/src/contracts/deepbook_predict/strike_exposure_config.ts +46 -0
  239. package/src/contracts/deepbook_predict/strike_payout_tree.ts +89 -0
  240. package/src/contracts/deepbook_predict/vault_events.ts +222 -0
  241. package/src/contracts/deepbook_sessions/config-arguments.ts +8 -0
  242. package/src/contracts/deepbook_sessions/deps/sui/vec_map.ts +33 -0
  243. package/src/contracts/deepbook_sessions/session_config.ts +130 -0
  244. package/src/contracts/deepbook_sessions/sessions.ts +663 -0
  245. package/src/contracts/propbook/block_scholes_store.ts +842 -0
  246. package/src/contracts/propbook/deps/sui/table.ts +37 -0
  247. package/src/contracts/propbook/oracle_lane.ts +170 -0
  248. package/src/contracts/propbook/pyth_feed.ts +414 -0
  249. package/src/contracts/propbook/registry.ts +596 -0
  250. package/src/deployments/index.ts +73 -0
  251. package/src/deployments/testnet.ts +107 -0
  252. package/src/deployments/types.ts +81 -0
  253. package/src/index.ts +5 -0
  254. package/src/predict/client.ts +941 -0
  255. package/src/predict/config/generated.ts +39 -0
  256. package/src/predict/config/index.ts +25 -0
  257. package/src/predict/config/testnet.ts +27 -0
  258. package/src/predict/config/types.ts +52 -0
  259. package/src/predict/decode.ts +476 -0
  260. package/src/predict/errors.ts +83 -0
  261. package/src/predict/index.ts +94 -0
  262. package/src/predict/pricing.ts +199 -0
  263. package/src/predict/reads/balances.ts +55 -0
  264. package/src/predict/reads/inspect.ts +78 -0
  265. package/src/predict/reads/markets.ts +222 -0
  266. package/src/predict/reads/parse.ts +30 -0
  267. package/src/predict/reads/pool.ts +37 -0
  268. package/src/predict/reads/positions.ts +144 -0
  269. package/src/predict/reads/pricing.ts +79 -0
  270. package/src/predict/ticks.ts +42 -0
  271. package/src/predict/tx/authed.ts +20 -0
  272. package/src/predict/tx/common.ts +92 -0
  273. package/src/predict/tx/trade.ts +183 -0
  274. package/src/predict/units.ts +49 -0
  275. package/src/sessions.ts +471 -0
  276. package/dist/types/bcs.mjs +0 -7
@@ -0,0 +1,941 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import type { ClientWithCoreApi, SuiClientRegistration } from '@mysten/sui/client';
4
+ import { Transaction, coinWithBalance, type TransactionResult } from '@mysten/sui/transactions';
5
+ import { isValidSuiObjectId } from '@mysten/sui/utils';
6
+ import { TESTNET_PREDICT } from '../deployments/testnet.js';
7
+ import { getConfig, type PredictConfig, type UnderlyingConfig } from './config/index.js';
8
+ import { toGeneratedConfig, type GeneratedConfig } from './config/generated.js';
9
+ import {
10
+ decodeAccountsCreated,
11
+ decodeBuilderCodeSets,
12
+ decodeClaims,
13
+ decodeDeposits,
14
+ decodeMints,
15
+ decodePlpCancels,
16
+ decodePlpRequests,
17
+ decodeRedeems,
18
+ decodeWithdrawals,
19
+ exactlyOne,
20
+ type DecodableTransactionResult,
21
+ } from './decode.js';
22
+ import { PredictInputError } from './errors.js';
23
+ import { simulateWithEvents } from './reads/inspect.js';
24
+ import {
25
+ positionsFromTable,
26
+ resolvePositionsTable,
27
+ type OpenPosition,
28
+ type PositionsHandle,
29
+ } from './reads/positions.js';
30
+ import { accountBalance, hasPosition } from './reads/balances.js';
31
+ import {
32
+ activeMarketIds,
33
+ currentNav,
34
+ expiryMarketId,
35
+ marketState,
36
+ marketStates,
37
+ rangePrices,
38
+ referenceTick,
39
+ type MarketState,
40
+ } from './reads/markets.js';
41
+ import { poolStats } from './reads/pool.js';
42
+ import { readPricerSnapshot, type PricerSnapshot } from './reads/pricing.js';
43
+ import { boardPricer, type BoardPricer } from './pricing.js';
44
+ import { POS_INF_TICK, binaryRangeTicks, type Side } from './ticks.js';
45
+ import {
46
+ cancelSupplyRequest,
47
+ cancelWithdrawRequest,
48
+ depositFunds,
49
+ requestSupply,
50
+ requestWithdraw,
51
+ setBuilderCode,
52
+ unsetBuilderCode,
53
+ withdrawFunds,
54
+ } from './tx/authed.js';
55
+
56
+ import { accountContract, deriveAccountWrapperIdFrom } from './tx/common.js';
57
+ import type { MarketFeeds } from './tx/trade.js';
58
+ import { mintExactAmount, mintExactQuantity, redeemLive, redeemSettled } from './tx/trade.js';
59
+ import {
60
+ priceToRaw,
61
+ probabilityToRaw,
62
+ rawToProbability,
63
+ rawToUsdc,
64
+ usdcToRaw,
65
+ fromRaw,
66
+ } from './units.js';
67
+
68
+ // `position_lot_size` — a position quantity must be a whole multiple of this many
69
+ // raw payout units ($0.01 lots). See packages/predict/sources/constants.move.
70
+ /**
71
+ * Testnet's `position_lot_size`, read from the deployment record. Validation uses the lot
72
+ * size of the config actually in play; this is the convenience constant for testnet callers.
73
+ */
74
+ export const POSITION_LOT_SIZE = BigInt(TESTNET_PREDICT.units.positionLotSize);
75
+
76
+ // Most `tx.*` builders are one builder's worth of commands in a fresh PTB.
77
+ function txOf(command: (tx: Transaction) => TransactionResult | void): Transaction {
78
+ const tx = new Transaction();
79
+ tx.add(command);
80
+ return tx;
81
+ }
82
+
83
+ /** A live/settled market addressed by its human coordinates: a binary position
84
+ * (single strike + side) or a two-strike range position. */
85
+ export type MarketDescriptor = {
86
+ underlying: string;
87
+ expiryMs: number | bigint;
88
+ /**
89
+ * Pin resolution to this exact `ExpiryMarket` object, skipping the
90
+ * underlying+expiry lookup — a caller that reviewed a specific market object
91
+ * mints against exactly that object, not whatever resolves at submit time.
92
+ */
93
+ marketId?: string;
94
+ } & (
95
+ | {
96
+ side: Side;
97
+ /**
98
+ * Strike in USD, or "reference" to trade at the market's on-chain reference
99
+ * price (the Polymarket-style anchor: derived from the exact previous-window
100
+ * oracle observation, so consecutive windows chain settlement → next strike).
101
+ */
102
+ strike: number | 'reference';
103
+ }
104
+ | {
105
+ /** A range position: pays out when settlement lands inside `(lower, upper]`
106
+ * (left-open, right-closed — same convention as the on-chain range key). */
107
+ side: 'range';
108
+ /** Lower strike bound in USD — finite, on the tick grid. */
109
+ lower: number;
110
+ /** Upper strike bound in USD — finite, on the tick grid, above `lower`. */
111
+ upper: number;
112
+ }
113
+ );
114
+
115
+ /** Options for the friendly `mint` (exact payout quantity). */
116
+ export interface MintOptions {
117
+ quantity: number;
118
+ maxCost?: number;
119
+ maxProbability?: number;
120
+ }
121
+
122
+ /** Options for `mintAmount` (spend up to a premium budget, floor the quantity received). */
123
+ export interface MintAmountOptions {
124
+ /** Premium budget in quote units — the max premium paid (chain also caps it at the account balance). */
125
+ spend: number;
126
+ minQuantity: number;
127
+ /** All-in cost ceiling in quote units (premium + fees). Omitted → uncapped. */
128
+ maxCost?: number;
129
+ }
130
+
131
+ /** Options for `redeem`: which order and how much to close. `claimSettled` takes only
132
+ * `orderId` — a settled claim closes the order in full. */
133
+ export interface CloseOptions {
134
+ orderId: bigint;
135
+ quantity: number;
136
+ }
137
+
138
+ /** One tradeable market as returned by read.markets(). */
139
+ export interface ActiveMarket {
140
+ id: string;
141
+ expiryMs: bigint;
142
+ /** Strike granularity in USD (e.g. 0.01). */
143
+ tickSize: number;
144
+ /**
145
+ * Coarser step new mint strikes must align to. A numeric strike must be a whole
146
+ * multiple of this (the market's `referencePrice` is the one exception the chain
147
+ * admits off-grid); otherwise the mint aborts `EInvalidAdmissionTick`.
148
+ */
149
+ admissionTickSize: number;
150
+ mintPaused: boolean;
151
+ /** The window's anchor strike in USD, or null until the keeper seeds it. */
152
+ referencePrice: number | null;
153
+ }
154
+
155
+ /** A resolved live market: its on-chain state summary for the caller. */
156
+ export interface MarketSummary {
157
+ id: string;
158
+ expiryMs: bigint;
159
+ tickSize: number;
160
+ /**
161
+ * Coarser step new mint strikes must align to. A numeric strike must be a whole
162
+ * multiple of this (the market's `referencePrice` is the one exception the chain
163
+ * admits off-grid); otherwise the mint aborts `EInvalidAdmissionTick`.
164
+ */
165
+ admissionTickSize: number;
166
+ mintPaused: boolean;
167
+ nav: number;
168
+ /** The window's anchor strike in USD, or null until the keeper seeds it. */
169
+ referencePrice: number | null;
170
+ }
171
+
172
+ /** Aggregate pool figures. Balances in human units (shares raw); the pending fields
173
+ * are request COUNTS, not amounts — the on-chain getters expose queue lengths, and
174
+ * the escrowed DUSDC/PLP behind them is tracked separately. */
175
+ export interface PoolSummary {
176
+ plpTotalSupply: bigint;
177
+ idleUsdc: number;
178
+ /** Number of LP supply requests queued for the next flush. */
179
+ supplyRequestsPending: number;
180
+ /** Number of LP withdraw requests queued for the next flush. */
181
+ withdrawRequestsPending: number;
182
+ }
183
+
184
+ /** Exact pre-trade quote: the dry-run receipt of the mint you are about to send. */
185
+ export interface MintQuote {
186
+ /** Fill price, 0..1 per $1 payout. */
187
+ entryProbability: number;
188
+ /** Premium paid into LP backing (quote units). */
189
+ premium: number;
190
+ /**
191
+ * Fee breakdown. `referral` is a PORTION of the trader-paid trading fee and
192
+ * congestion surcharge routed to the referrer — it is already inside those
193
+ * numbers and is NOT an extra debit. `inventoryImpact` is a separate charge and
194
+ * IS part of `cost`.
195
+ */
196
+ fees: {
197
+ trading: number;
198
+ subsidy: number;
199
+ builder: number;
200
+ penalty: number;
201
+ referral: number;
202
+ inventoryImpact: number;
203
+ };
204
+ /**
205
+ * All-in account debit: premium + (trading − subsidy) + builder + penalty +
206
+ * inventoryImpact — exactly what the chain withdraws (the deployed
207
+ * `compute_mint_quote`'s `all_in_cost`); pass this (plus your buffer) as maxCost.
208
+ */
209
+ cost: number;
210
+ quantity: number;
211
+ raw: { premium: bigint; cost: bigint; quantity: bigint; entryProbability: bigint };
212
+ /** True: computed by the real mint code path against real account state. */
213
+ feesExact: true;
214
+ }
215
+
216
+ /** Exact pre-close quote: the dry-run receipt of the redeem you are about to send. */
217
+ export interface RedeemQuote {
218
+ /** NET quote credited to the account. */
219
+ proceeds: number;
220
+ /** Gross close value before fees. */
221
+ gross: number;
222
+ /** `inventoryImpactRebate` is credited back on the close, so `proceeds` is
223
+ * gross + rebate − trading − builder − penalty. */
224
+ fees: { trading: number; builder: number; penalty: number; inventoryImpactRebate: number };
225
+ quantityClosed: number;
226
+ remaining: number;
227
+ raw: { proceeds: bigint; gross: bigint; quantityClosed: bigint };
228
+ feesExact: true;
229
+ }
230
+
231
+ interface ResolvedMarket {
232
+ id: string;
233
+ state: MarketState;
234
+ }
235
+
236
+ // The strike-bearing (binary) arm of MarketDescriptor, for read.price and its
237
+ // seam — anonymous board pricing has no range semantics.
238
+ type BinaryMarketCoordinates = Pick<MarketDescriptor, 'underlying' | 'expiryMs' | 'marketId'> & {
239
+ strike: number | 'reference';
240
+ };
241
+
242
+ /** The Sui client surface PredictClient reads through: any `ClientWithCoreApi`
243
+ * (gRPC or JSON-RPC) provides both the `simulateTransaction` the reads/quotes
244
+ * sit on and the `core` object methods position enumeration needs. */
245
+ export interface PredictCompatibleClient extends ClientWithCoreApi {}
246
+
247
+ /**
248
+ * Register PredictClient as a `client.predict` extension, mirroring
249
+ * `@mysten/deepbook-v3`'s `deepbook(...)`: `client.$extend(predict({ network }))`.
250
+ */
251
+ export function predict<Name extends string = 'predict'>({
252
+ name = 'predict' as Name,
253
+ network,
254
+ config,
255
+ }: {
256
+ name?: Name;
257
+ network: 'testnet' | 'mainnet';
258
+ config?: PredictConfig;
259
+ }): SuiClientRegistration<PredictCompatibleClient, Name, PredictClient> {
260
+ return {
261
+ name,
262
+ register: (client) => new PredictClient({ client, network, config }),
263
+ };
264
+ }
265
+
266
+ /**
267
+ * The one object an app constructs. Wraps the config, a client for reads, and
268
+ * a derived-account model so callers pass owner addresses, decimal amounts, and
269
+ * human market coordinates — the facade converts to raw units, resolves markets
270
+ * (cached), and delegates to the internal tx primitives / reads. Callers who need
271
+ * to compose their own PTBs can use the generated bindings under `contracts/`.
272
+ */
273
+ export class PredictClient {
274
+ readonly cfg: PredictConfig;
275
+ // The flat slice every generated call resolves `options.config` against.
276
+ get #config(): GeneratedConfig {
277
+ return toGeneratedConfig(this.cfg);
278
+ }
279
+ #client: PredictCompatibleClient;
280
+ // underlying:expiryMs → resolved market. The id and tickSizeRaw — the only
281
+ // state tx building depends on — are immutable per (underlying, expiry), so
282
+ // one resolution per market per client suffices. (mintPaused IS mutable; the
283
+ // cached copy is never consulted for a tx decision — the chain enforces it.)
284
+ #marketCache = new Map<string, ResolvedMarket>();
285
+ // owner → resolved position-store ids. accountUid and the table id are
286
+ // immutable once created, so cache-forever; a missing table (no Predict
287
+ // data yet) is NOT cached — it appears after the owner's first trade.
288
+ #positionsCache = new Map<string, PositionsHandle>();
289
+
290
+ constructor(opts: {
291
+ network: 'testnet' | 'mainnet';
292
+ client: PredictCompatibleClient;
293
+ config?: PredictConfig;
294
+ }) {
295
+ this.cfg = opts.config ?? getConfig(opts.network);
296
+ this.#client = opts.client;
297
+ }
298
+
299
+ /** The deterministic id of an owner's canonical account wrapper — no chain read. */
300
+ wrapperIdFor(owner: string): string {
301
+ return deriveAccountWrapperIdFrom(this.#config, owner);
302
+ }
303
+
304
+ // The deployment's wiring for a symbol; throws a typed error on an unknown symbol.
305
+ // Per-underlying ids are the one thing the flat config slice does not carry.
306
+ #underlying(underlying: string): UnderlyingConfig {
307
+ const u = this.cfg.underlyings[underlying];
308
+ if (!u) throw new PredictInputError(`unknown underlying: ${underlying}`);
309
+ return u;
310
+ }
311
+
312
+ // The oracle feed ids for a symbol; throws a typed error on an unknown symbol.
313
+ #feeds(underlying: string): MarketFeeds {
314
+ const u = this.#underlying(underlying);
315
+ return {
316
+ pythFeed: u.pythFeed,
317
+ blockScholesValueStore: u.blockScholesValueStore,
318
+ blockScholesSviStore: u.blockScholesSviStore,
319
+ };
320
+ }
321
+
322
+ // Resolve (and cache) a market's id + state from its human coordinates. An
323
+ // explicit `marketId` pin skips the underlying+expiry lookup but still reads
324
+ // that market's state — tx building depends on tickSizeRaw.
325
+ async #resolveMarket(
326
+ m: Pick<MarketDescriptor, 'underlying' | 'expiryMs' | 'marketId'>,
327
+ ): Promise<ResolvedMarket> {
328
+ if (m.marketId != null) {
329
+ if (!isValidSuiObjectId(m.marketId)) {
330
+ throw new PredictInputError(`invalid marketId: ${JSON.stringify(m.marketId)}`);
331
+ }
332
+ const resolved: ResolvedMarket = this.#marketCache.get(m.marketId) ?? {
333
+ id: m.marketId,
334
+ state: await marketState(this.#client, this.#config, m.marketId),
335
+ };
336
+ // The pin must agree with the descriptor's coordinates: catching a stale or
337
+ // wrong-market id here beats minting against mismatched oracle feeds. (The
338
+ // underlying cannot be cross-checked — market state does not carry it.)
339
+ if (resolved.state.expiryMs !== BigInt(m.expiryMs)) {
340
+ throw new PredictInputError(
341
+ `pinned market ${m.marketId} expires at ${resolved.state.expiryMs}, descriptor says ${BigInt(m.expiryMs)}`,
342
+ );
343
+ }
344
+ this.#marketCache.set(m.marketId, resolved);
345
+ return resolved;
346
+ }
347
+ const expiryMs = BigInt(m.expiryMs);
348
+ const key = `${m.underlying}:${expiryMs}`;
349
+ const hit = this.#marketCache.get(key);
350
+ if (hit) return hit;
351
+ const u = this.#underlying(m.underlying);
352
+ const id = await expiryMarketId(this.#client, this.#config, u, expiryMs);
353
+ if (!id) throw new PredictInputError(`no market for ${m.underlying} at expiry ${expiryMs}`);
354
+ const state = await marketState(this.#client, this.#config, id);
355
+ const resolved: ResolvedMarket = { id, state };
356
+ this.#marketCache.set(key, resolved);
357
+ return resolved;
358
+ }
359
+
360
+ // Reference PRICE in USD from a state (tick index × tick size), or null.
361
+ static #referencePriceOf(state: MarketState): number | null {
362
+ return state.referenceTickRaw == null
363
+ ? null
364
+ : fromRaw(state.referenceTickRaw * state.tickSizeRaw, 9);
365
+ }
366
+
367
+ // A finite tick from a USD strike, validated exactly like binaryRangeTicks:
368
+ // whole-tick multiple, inside the finite domain (1..POS_INF_TICK-1).
369
+ #gridTick(strike: number, tickSizeRaw: bigint): bigint {
370
+ const raw = priceToRaw(strike);
371
+ const tick = raw / tickSizeRaw;
372
+ if (tick * tickSizeRaw !== raw) {
373
+ throw new PredictInputError(
374
+ `strike ${strike} is not on the ${fromRaw(tickSizeRaw, 9)} tick grid`,
375
+ );
376
+ }
377
+ if (tick <= 0n || tick >= POS_INF_TICK) {
378
+ throw new PredictInputError(
379
+ `strike tick ${tick} outside the finite tick domain (1..POS_INF_TICK-1)`,
380
+ );
381
+ }
382
+ return tick;
383
+ }
384
+
385
+ // New finite MINT boundaries must land on the market's coarser ADMISSION grid,
386
+ // not merely the fine tick grid — the chain asserts exactly this
387
+ // (`assert_admitted_mint_ticks`, `EInvalidAdmissionTick`). The ±inf sentinels are
388
+ // exempt, and the market's reference tick is the one finite boundary allowed to
389
+ // bypass the grid, so an off-grid tick is only rejected after confirming it is not
390
+ // the reference (one extra read, and only on the failing path).
391
+ async #assertAdmittedTick(tick: bigint, marketId: string, state: MarketState): Promise<void> {
392
+ if (tick === 0n || tick === POS_INF_TICK) return;
393
+ const multiple = state.admissionTickSizeRaw / state.tickSizeRaw;
394
+ if (multiple > 0n && tick % multiple === 0n) return;
395
+ const reference = await referenceTick(this.#client, this.#config, marketId);
396
+ if (reference != null && reference === tick) return;
397
+ const admission = fromRaw(state.admissionTickSizeRaw, 9);
398
+ throw new PredictInputError(
399
+ `strike ${fromRaw(tick * state.tickSizeRaw, 9)} is not on the ${admission} admission grid ` +
400
+ `(mint boundaries must be a multiple of ${admission}, or the market's reference strike)`,
401
+ );
402
+ }
403
+
404
+ // Resolve a descriptor's strike(s) to the (lower, higher) tick pair. A binary
405
+ // numeric strike converts and validates against the tick grid; "reference"
406
+ // reads the market's reference tick FRESH (never cached — it is unset early in
407
+ // a window) and uses it directly: it is on the tick grid by construction. A
408
+ // range descriptor converts both bounds to finite grid ticks ("reference" is
409
+ // binary-only: a range has no single reference strike).
410
+ async #strikeTicks(
411
+ m: MarketDescriptor,
412
+ marketId: string,
413
+ state: MarketState,
414
+ ): Promise<{ lowerTick: bigint; higherTick: bigint }> {
415
+ if (m.side === 'range') {
416
+ if (!(m.lower < m.upper)) {
417
+ throw new PredictInputError(`range lower ${m.lower} must be below upper ${m.upper}`);
418
+ }
419
+ const lowerTick = this.#gridTick(m.lower, state.tickSizeRaw);
420
+ const higherTick = this.#gridTick(m.upper, state.tickSizeRaw);
421
+ await this.#assertAdmittedTick(lowerTick, marketId, state);
422
+ await this.#assertAdmittedTick(higherTick, marketId, state);
423
+ return { lowerTick, higherTick };
424
+ }
425
+ if (m.strike !== 'reference') {
426
+ const ticks = binaryRangeTicks(priceToRaw(m.strike), m.side, state.tickSizeRaw);
427
+ await this.#assertAdmittedTick(ticks.lowerTick, marketId, state);
428
+ await this.#assertAdmittedTick(ticks.higherTick, marketId, state);
429
+ return ticks;
430
+ }
431
+ const tick = await referenceTick(this.#client, this.#config, marketId);
432
+ if (tick == null) {
433
+ throw new PredictInputError(
434
+ `reference price not set yet for ${m.underlying} @ ${m.expiryMs} — retry shortly or pass a numeric strike`,
435
+ );
436
+ }
437
+ return m.side === 'up'
438
+ ? { lowerTick: tick, higherTick: POS_INF_TICK }
439
+ : { lowerTick: 0n, higherTick: tick };
440
+ }
441
+
442
+ // Raw payout quantity must land on a lot boundary — the chain rejects otherwise.
443
+ #assertLot(quantityRaw: bigint): void {
444
+ // From the config in play, not the exported testnet constant — a deployment with a
445
+ // different `position_lot_size` must not be validated against testnet's.
446
+ const lot = BigInt(this.cfg.units.positionLotSize);
447
+ if (quantityRaw % lot !== 0n) {
448
+ throw new PredictInputError(
449
+ `quantity ${quantityRaw} raw is not a whole ${lot}-unit lot (position_lot_size)`,
450
+ );
451
+ }
452
+ }
453
+
454
+ // Shared construction for tx.mint and read.quoteMint. The quote dry-runs the
455
+ // same mint the trade sends; quoteMint omits the caller's cost/probability caps
456
+ // (they only gate via abort and don't change the receipt numbers).
457
+ async #buildMint(owner: string, m: MarketDescriptor, opts: MintOptions): Promise<Transaction> {
458
+ const feeds = this.#feeds(m.underlying);
459
+ const { id, state } = await this.#resolveMarket(m);
460
+ const quantityRaw = usdcToRaw(opts.quantity);
461
+ this.#assertLot(quantityRaw);
462
+ const { lowerTick, higherTick } = await this.#strikeTicks(m, id, state);
463
+ return txOf(
464
+ mintExactQuantity(this.#config, {
465
+ expiryMarketId: id,
466
+ wrapperId: this.wrapperIdFor(owner),
467
+ lowerTick,
468
+ higherTick,
469
+ quantityRaw,
470
+ maxCostRaw: opts.maxCost != null ? usdcToRaw(opts.maxCost) : undefined,
471
+ maxProbabilityRaw:
472
+ opts.maxProbability != null ? probabilityToRaw(opts.maxProbability) : undefined,
473
+ ...feeds,
474
+ }),
475
+ );
476
+ }
477
+
478
+ // Shared construction for tx.redeem and read.quoteRedeem.
479
+ async #buildRedeem(owner: string, m: MarketDescriptor, opts: CloseOptions): Promise<Transaction> {
480
+ const feeds = this.#feeds(m.underlying);
481
+ const { id } = await this.#resolveMarket(m);
482
+ const closeQuantityRaw = usdcToRaw(opts.quantity);
483
+ this.#assertLot(closeQuantityRaw);
484
+ return txOf(
485
+ redeemLive(this.#config, {
486
+ expiryMarketId: id,
487
+ wrapperId: this.wrapperIdFor(owner),
488
+ orderId: opts.orderId,
489
+ closeQuantityRaw,
490
+ ...feeds,
491
+ }),
492
+ );
493
+ }
494
+
495
+ // Raw strike for anonymous pricing: numeric strikes validate against the tick
496
+ // grid; "reference" reads the market's reference tick fresh (unset → typed error).
497
+ async #strikeRawFor(
498
+ m: BinaryMarketCoordinates,
499
+ marketId: string,
500
+ state: MarketState,
501
+ ): Promise<bigint> {
502
+ if (m.strike !== 'reference') {
503
+ // Same validation as the mint path: on the grid AND inside the finite tick
504
+ // domain (0 / POS_INF are the ±inf sentinels, not quotable strikes).
505
+ return this.#gridTick(m.strike, state.tickSizeRaw) * state.tickSizeRaw;
506
+ }
507
+ const tick = await referenceTick(this.#client, this.#config, marketId);
508
+ if (tick == null) {
509
+ throw new PredictInputError(
510
+ `reference price not set yet for ${m.underlying} @ ${m.expiryMs} — retry shortly or pass a numeric strike`,
511
+ );
512
+ }
513
+ return tick * state.tickSizeRaw;
514
+ }
515
+
516
+ // === tx builders ===
517
+ // Each returns a ready-to-sign Transaction. Market-resolving builders are async.
518
+ readonly tx = {
519
+ createManager: (): Transaction => txOf(accountContract(this.cfg).createAccount()),
520
+
521
+ // `create: true` composes first-time funding into ONE PTB: create the account
522
+ // wrapper, deposit into it through the fresh handle, and `share` it LAST (once
523
+ // shared, by-value use of the handle is over). The wrapper is derived from the
524
+ // transaction SENDER (`account_registry::new` takes no owner), so `owner` MUST
525
+ // be the address that signs this transaction — a sponsored/backend signer would
526
+ // silently fund its own fresh account instead. The caller also asserts the
527
+ // account does not exist yet: `new` ABORTS at the deterministic address if it
528
+ // already exists — no chain read is done here. Gate on your own existence check
529
+ // (`wrapperIdFor(owner)` + a getObject), or retry without the flag on that abort.
530
+ //
531
+ // Without `create`, the sourced coin goes into the existing account's stored
532
+ // balance via the PTB-callable `deposit_funds` (folds settle → authorize → load →
533
+ // deposit; clock auto-injected). Command order is auth → deposit (auth is a hot
534
+ // potato consumed by the deposit). See
535
+ // `packages/account/sources/account.move` (`deposit_funds`).
536
+ deposit: (
537
+ owner: string,
538
+ amountUsdc: number | string,
539
+ opts?: { create?: boolean },
540
+ ): Transaction => {
541
+ const tx = new Transaction();
542
+ const coin = tx.add(
543
+ coinWithBalance({
544
+ type: this.cfg.quoteCoinType,
545
+ balance: usdcToRaw(amountUsdc),
546
+ useGasCoin: false,
547
+ }),
548
+ );
549
+ if (opts?.create) {
550
+ tx.add(
551
+ accountContract(this.cfg).createAccountAndDeposit({
552
+ coin,
553
+ coinType: this.cfg.quoteCoinType,
554
+ }),
555
+ );
556
+ } else {
557
+ tx.add(
558
+ depositFunds({
559
+ config: this.#config,
560
+ arguments: { wrapper: this.wrapperIdFor(owner), coin },
561
+ typeArguments: [this.cfg.quoteCoinType],
562
+ }),
563
+ );
564
+ }
565
+ return tx;
566
+ },
567
+
568
+ // Withdraw `amountUsdc` from the account back to `owner`. By default the funds land
569
+ // in the owner's DUSDC *address balance* (the versionless accumulator) via
570
+ // `0x2::coin::send_funds` — no coin-object churn, and they merge into the same
571
+ // balance `deposit` draws from, closing the loop. Pass `{ toCoinObject: true }` to
572
+ // instead receive a discrete `Coin<T>` object (for wallets/explorers that only
573
+ // render coin objects, or to compose the coin further in your own PTB). Either way
574
+ // the underlying `withdraw_funds` returns the raw `Coin<T>` — the PTB-callable form
575
+ // that folds settle → authorize → load → withdraw (clock auto-injected, `ctx`
576
+ // implicit); command order is auth → withdraw. See
577
+ // `packages/account/sources/account.move` (`withdraw_funds`).
578
+ withdraw: (
579
+ owner: string,
580
+ amountUsdc: number | string,
581
+ opts?: { toCoinObject?: boolean },
582
+ ): Transaction => {
583
+ const tx = new Transaction();
584
+ const coin = tx.add(
585
+ withdrawFunds({
586
+ config: this.#config,
587
+ arguments: { wrapper: this.wrapperIdFor(owner), amount: usdcToRaw(amountUsdc) },
588
+ typeArguments: [this.cfg.quoteCoinType],
589
+ }),
590
+ );
591
+ if (opts?.toCoinObject) {
592
+ tx.transferObjects([coin], owner);
593
+ } else {
594
+ tx.moveCall({
595
+ target: '0x2::coin::send_funds',
596
+ typeArguments: [this.cfg.quoteCoinType],
597
+ arguments: [coin, tx.pure.address(owner)],
598
+ });
599
+ }
600
+ return tx;
601
+ },
602
+
603
+ mint: (owner: string, m: MarketDescriptor, opts: MintOptions): Promise<Transaction> =>
604
+ this.#buildMint(owner, m, opts),
605
+
606
+ mintAmount: async (
607
+ owner: string,
608
+ m: MarketDescriptor,
609
+ opts: MintAmountOptions,
610
+ ): Promise<Transaction> => {
611
+ const feeds = this.#feeds(m.underlying);
612
+ // The chain requires a positive all-in cost cap (EMintCostCapRequired);
613
+ // reject a zero cap pre-flight rather than surface a cryptic Move abort.
614
+ if (opts.maxCost != null && opts.maxCost <= 0) {
615
+ throw new PredictInputError('maxCost must be > 0');
616
+ }
617
+ const { id, state } = await this.#resolveMarket(m);
618
+ // No lot check: min_quantity is a floor the chain compares against an
619
+ // already-lot-floored minted quantity, so any floor value is legal.
620
+ const minQuantityRaw = usdcToRaw(opts.minQuantity);
621
+ const { lowerTick, higherTick } = await this.#strikeTicks(m, id, state);
622
+ return txOf(
623
+ mintExactAmount(this.#config, {
624
+ expiryMarketId: id,
625
+ wrapperId: this.wrapperIdFor(owner),
626
+ lowerTick,
627
+ higherTick,
628
+ maxPremiumRaw: usdcToRaw(opts.spend),
629
+ minQuantityRaw,
630
+ maxCostRaw: opts.maxCost != null ? usdcToRaw(opts.maxCost) : undefined,
631
+ ...feeds,
632
+ }),
633
+ );
634
+ },
635
+
636
+ redeem: (owner: string, m: MarketDescriptor, opts: CloseOptions): Promise<Transaction> =>
637
+ this.#buildRedeem(owner, m, opts),
638
+
639
+ claimSettled: async (
640
+ owner: string,
641
+ m: Pick<MarketDescriptor, 'underlying' | 'expiryMs' | 'marketId'>,
642
+ opts: Pick<CloseOptions, 'orderId'>,
643
+ ): Promise<Transaction> => {
644
+ const { id } = await this.#resolveMarket(m);
645
+ return txOf(
646
+ redeemSettled(this.#config, {
647
+ expiryMarketId: id,
648
+ wrapperId: this.wrapperIdFor(owner),
649
+ orderId: opts.orderId,
650
+ }),
651
+ );
652
+ },
653
+
654
+ // Queue a supply request pulling `amountUsdc` from the account's existing custody
655
+ // balance. `request_supply` auto-settles DUSDC then `account.withdraw`s the payment
656
+ // into queue escrow; the PLP fill is delivered at the next flush, not returned here.
657
+ // Command order is auth → request (auth is a hot potato consumed by this call). The
658
+ // `minPlpOut` slot is the per-request floor on PLP minted at flush — pinned to 0
659
+ // (no floor) here; after three flushes miss the floor the request is cancelled and
660
+ // refunded.
661
+ supplyPlp: (owner: string, amountUsdc: number | string): Transaction =>
662
+ txOf(
663
+ requestSupply({
664
+ config: this.#config,
665
+ arguments: {
666
+ wrapper: this.wrapperIdFor(owner),
667
+ amount: usdcToRaw(amountUsdc),
668
+ minPlpOut: 0n,
669
+ },
670
+ }),
671
+ ),
672
+
673
+ // Queue a withdraw request pulling `shares` (raw PLP u64) from account custody into
674
+ // queue escrow — the Move parameter is named `amount`, but on `request_withdraw` it
675
+ // counts PLP SHARES, not DUSDC. Auto-settles flush-delivered PLP first; the DUSDC
676
+ // fill lands on the account at the next flush (no `withdraw_settled` entrypoint).
677
+ // Command order is auth → request. The `minDusdcOut` slot is the per-request floor
678
+ // on DUSDC paid at flush — pinned to 0 (no floor) here; after three flushes miss the
679
+ // floor the request is cancelled and refunded.
680
+ withdrawPlp: (owner: string, shares: bigint): Transaction =>
681
+ txOf(
682
+ requestWithdraw({
683
+ config: this.#config,
684
+ arguments: {
685
+ wrapper: this.wrapperIdFor(owner),
686
+ amount: shares,
687
+ minDusdcOut: 0n,
688
+ },
689
+ }),
690
+ ),
691
+
692
+ // Cancel a still-pending supply request by queue `index`, refunding its escrowed
693
+ // DUSDC straight back into the requesting account. Command order is auth → cancel.
694
+ cancelSupplyPlp: (owner: string, index: bigint): Transaction =>
695
+ txOf(
696
+ cancelSupplyRequest({
697
+ config: this.#config,
698
+ arguments: { wrapper: this.wrapperIdFor(owner), index },
699
+ }),
700
+ ),
701
+
702
+ // Cancel a still-pending withdraw request by queue `index`, refunding its escrowed
703
+ // PLP straight back into the requesting account. Command order is auth → cancel.
704
+ cancelWithdrawPlp: (owner: string, index: bigint): Transaction =>
705
+ txOf(
706
+ cancelWithdrawRequest({
707
+ config: this.#config,
708
+ arguments: { wrapper: this.wrapperIdFor(owner), index },
709
+ }),
710
+ ),
711
+
712
+ // Set the account's sticky builder-code attribution to `builderCodeId`, an existing
713
+ // `BuilderCode` object borrowed as `&BuilderCode`. Command order is auth → set (auth
714
+ // is a hot potato consumed by this call). Lives in the PREDICT package's
715
+ // `predict_account` module, NOT the account package. Deployed sig
716
+ // `packages/predict/sources/predict_account.move:134` — 3 moveCall args
717
+ // (wrapper, auth, code; ctx implicit).
718
+ setBuilderCode: (owner: string, builderCodeId: string): Transaction =>
719
+ txOf(
720
+ setBuilderCode({
721
+ config: this.#config,
722
+ arguments: { wrapper: this.wrapperIdFor(owner), code: builderCodeId },
723
+ }),
724
+ ),
725
+
726
+ // Clear the account's sticky builder-code attribution. Command order is auth → unset.
727
+ // Deployed sig `.../predict_account.move:151` — 2 moveCall args (wrapper, auth; ctx
728
+ // implicit).
729
+ unsetBuilderCode: (owner: string): Transaction =>
730
+ txOf(
731
+ unsetBuilderCode({
732
+ config: this.#config,
733
+ arguments: { wrapper: this.wrapperIdFor(owner) },
734
+ }),
735
+ ),
736
+ };
737
+
738
+ // === reads ===
739
+ readonly read = {
740
+ // All tradeable (active) markets with the state a frontend needs to render
741
+ // and mint: one chain read for ids + one batched PTB for the states.
742
+ markets: async (): Promise<ActiveMarket[]> => {
743
+ const ids = await activeMarketIds(this.#client, this.#config);
744
+ const states = await marketStates(this.#client, this.#config, ids);
745
+ return ids.map((id, i) => ({
746
+ id,
747
+ expiryMs: states[i].expiryMs,
748
+ tickSize: fromRaw(states[i].tickSizeRaw, 9),
749
+ admissionTickSize: fromRaw(states[i].admissionTickSizeRaw, 9),
750
+ mintPaused: states[i].mintPaused,
751
+ referencePrice: PredictClient.#referencePriceOf(states[i]),
752
+ }));
753
+ },
754
+
755
+ // Validate an app-stored order id against the chain (stale after full
756
+ // close or partial-close replacement — see RedeemReceipt.replacementOrderId).
757
+ hasPosition: (owner: string, marketId: string, orderId: bigint): Promise<boolean> =>
758
+ hasPosition(this.#client, this.#config, owner, marketId, orderId),
759
+
760
+ // All open positions for an owner, enumerated from the chain (the
761
+ // account's positions Table): 1 call per page warm, +2 resolution calls
762
+ // once per owner. Returns [] for owners with no Predict account.
763
+ positions: async (owner: string): Promise<OpenPosition[]> => {
764
+ let handle = this.#positionsCache.get(owner);
765
+ if (!handle?.positionsTableId) {
766
+ const resolved = await resolvePositionsTable(this.#client, this.#config, owner);
767
+ if (!resolved) return []; // never onboarded — do not cache
768
+ if (resolved.positionsTableId) this.#positionsCache.set(owner, resolved);
769
+ handle = resolved;
770
+ }
771
+ if (!handle.positionsTableId) return [];
772
+ return positionsFromTable(this.#client, handle.positionsTableId);
773
+ },
774
+
775
+ // Anonymous board pricing: the chain's probability for both sides of a
776
+ // strike, from one fresh pricer (no account needed). This is the ↑/↓
777
+ // button price before a user has onboarded.
778
+ price: async (m: BinaryMarketCoordinates): Promise<{ up: number; down: number }> => {
779
+ const feeds = this.#feeds(m.underlying);
780
+ const { id, state } = await this.#resolveMarket(m);
781
+ const strikeRaw = await this.#strikeRawFor(m, id, state);
782
+ const { upRaw, downRaw } = await rangePrices(
783
+ this.#client,
784
+ this.#config,
785
+ id,
786
+ feeds,
787
+ strikeRaw,
788
+ state.tickSizeRaw,
789
+ );
790
+ return { up: rawToProbability(upRaw), down: rawToProbability(downRaw) };
791
+ },
792
+
793
+ // A client-side board pricer for one market: ONE simulate reads the chain's
794
+ // resolved pricer (already forward-selected + rolled to now), then prices every
795
+ // strike LOCALLY with no further chain calls — `pricer.up(strike)`,
796
+ // `.down(strike)`, `.range(lo,hi)`, `.strikeAtProbability(p)`. Use this to paint a
797
+ // whole board instantly; `read.price` / `read.quoteMint` stay the authoritative
798
+ // per-strike quote at trade time. Throws the same typed stale-oracle/expired
799
+ // PredictMoveError `read.price` would when the chain itself cannot quote.
800
+ pricer: async (
801
+ m: Pick<MarketDescriptor, 'underlying' | 'expiryMs'>,
802
+ ): Promise<BoardPricer & { asOf: PricerSnapshot['sources'] }> => {
803
+ const feeds = this.#feeds(m.underlying);
804
+ const { id } = await this.#resolveMarket(m);
805
+ const snap = await readPricerSnapshot(this.#client, this.#config, id, feeds);
806
+ return { ...boardPricer(snap), asOf: snap.sources };
807
+ },
808
+
809
+ // Exact pre-trade quote: dry-runs the caller's own mint (same tx as
810
+ // tx.mint) and decodes the receipt. Requires a funded account; throws
811
+ // the same typed errors the real trade would — quote doubles as preflight.
812
+ quoteMint: async (
813
+ owner: string,
814
+ m: MarketDescriptor,
815
+ opts: Pick<MintOptions, 'quantity'>,
816
+ ): Promise<MintQuote> => {
817
+ const tx = await this.#buildMint(owner, m, opts);
818
+ const events = await simulateWithEvents(this.#client, tx, owner);
819
+ const r = exactlyOne(decodeMints(this.cfg, { events }), 'OrderMinted');
820
+ // Mirrors the deployed `compute_mint_quote`'s all_in_cost exactly:
821
+ // premium + (trading − subsidy) + builder + penalty + inventory-impact.
822
+ // `referral_fee` is deliberately NOT added — it is a portion OF the
823
+ // trader-paid trading fee and congestion surcharge, not an extra debit.
824
+ const costRaw =
825
+ r.raw.premium +
826
+ (r.raw.tradingFee - r.raw.feeIncentiveSubsidy) +
827
+ r.raw.builderFee +
828
+ r.raw.penaltyFee +
829
+ r.raw.inventoryImpactCharge;
830
+ return {
831
+ entryProbability: r.entryProbability,
832
+ premium: r.premium,
833
+ fees: r.fees,
834
+ cost: rawToUsdc(costRaw),
835
+ quantity: r.quantity,
836
+ raw: {
837
+ premium: r.raw.premium,
838
+ cost: costRaw,
839
+ quantity: r.raw.quantity,
840
+ entryProbability: r.raw.entryProbability,
841
+ },
842
+ feesExact: true,
843
+ };
844
+ },
845
+
846
+ // Exact pre-close quote: dry-runs the caller's own redeem and decodes
847
+ // the receipt — the informed close against the floor-less deployed redeem.
848
+ quoteRedeem: async (
849
+ owner: string,
850
+ m: MarketDescriptor,
851
+ opts: CloseOptions,
852
+ ): Promise<RedeemQuote> => {
853
+ const tx = await this.#buildRedeem(owner, m, opts);
854
+ const events = await simulateWithEvents(this.#client, tx, owner);
855
+ const r = exactlyOne(decodeRedeems(this.cfg, { events }), 'order-redeemed');
856
+ return {
857
+ proceeds: r.proceeds,
858
+ gross: r.gross,
859
+ fees: r.fees,
860
+ quantityClosed: r.quantityClosed,
861
+ remaining: r.remaining,
862
+ raw: {
863
+ proceeds: r.raw.proceeds,
864
+ gross: r.raw.gross,
865
+ quantityClosed: r.raw.quantityClosed,
866
+ },
867
+ feesExact: true,
868
+ };
869
+ },
870
+
871
+ market: async (
872
+ m: Pick<MarketDescriptor, 'underlying' | 'expiryMs'>,
873
+ ): Promise<MarketSummary | null> => {
874
+ const expiryMs = BigInt(m.expiryMs);
875
+ // Deliberately re-queries and overwrites the cache instead of reading
876
+ // through it: this read must return live state (nav, mintPaused), and
877
+ // refreshing the cache on the way keeps later tx builds consistent.
878
+ const u = this.#underlying(m.underlying);
879
+ const id = await expiryMarketId(this.#client, this.#config, u, expiryMs);
880
+ if (!id) return null;
881
+ const state = await marketState(this.#client, this.#config, id);
882
+ this.#marketCache.set(`${m.underlying}:${expiryMs}`, { id, state });
883
+ const navRaw = await currentNav(this.#client, this.#config, id, u);
884
+ return {
885
+ id,
886
+ expiryMs: state.expiryMs,
887
+ tickSize: fromRaw(state.tickSizeRaw, 9), // strike/price scale
888
+ admissionTickSize: fromRaw(state.admissionTickSizeRaw, 9),
889
+ mintPaused: state.mintPaused,
890
+ nav: rawToUsdc(navRaw),
891
+ referencePrice: PredictClient.#referencePriceOf(state),
892
+ };
893
+ },
894
+
895
+ balance: async (owner: string): Promise<number> =>
896
+ rawToUsdc(await accountBalance(this.#client, this.#config, owner, this.cfg.quoteCoinType)),
897
+
898
+ // PLP shares held in the owner's account custody (raw u64, 6-decimal PLP coin).
899
+ plpBalance: (owner: string): Promise<bigint> =>
900
+ accountBalance(this.#client, this.#config, owner, this.cfg.coinTypes.plp),
901
+
902
+ pool: async (): Promise<PoolSummary> => {
903
+ const s = await poolStats(this.#client, this.#config);
904
+ return {
905
+ plpTotalSupply: s.plpTotalSupply, // shares raw (6-decimal)
906
+ idleUsdc: rawToUsdc(s.idleBalance),
907
+ // These are queue LENGTHS (counts of pending requests), not token amounts.
908
+ supplyRequestsPending: Number(s.supplyRequestsPending),
909
+ withdrawRequestsPending: Number(s.withdrawRequestsPending),
910
+ };
911
+ },
912
+ };
913
+
914
+ // === execution-result decoders ===
915
+ // Pure event parsing (no network): pass the executed/simulated transaction
916
+ // result (with events included) and get a typed receipt back. Singular forms
917
+ // throw unless exactly one matching event exists; plural forms return all
918
+ // (an integrator batching N actions in one PTB gets N receipts).
919
+ readonly decode = {
920
+ mint: (r: DecodableTransactionResult) => exactlyOne(decodeMints(this.cfg, r), 'OrderMinted'),
921
+ mints: (r: DecodableTransactionResult) => decodeMints(this.cfg, r),
922
+ redeem: (r: DecodableTransactionResult) =>
923
+ exactlyOne(decodeRedeems(this.cfg, r), 'order-redeemed'),
924
+ redeems: (r: DecodableTransactionResult) => decodeRedeems(this.cfg, r),
925
+ claim: (r: DecodableTransactionResult) =>
926
+ exactlyOne(decodeClaims(this.cfg, r), 'SettledOrderRedeemed'),
927
+ claims: (r: DecodableTransactionResult) => decodeClaims(this.cfg, r),
928
+ createManager: (r: DecodableTransactionResult) =>
929
+ exactlyOne(decodeAccountsCreated(this.cfg, r), 'AccountCreated'),
930
+ deposit: (r: DecodableTransactionResult) =>
931
+ exactlyOne(decodeDeposits(this.cfg, r), 'Deposited'),
932
+ withdraw: (r: DecodableTransactionResult) =>
933
+ exactlyOne(decodeWithdrawals(this.cfg, r), 'Withdrawn'),
934
+ plpRequest: (r: DecodableTransactionResult) =>
935
+ exactlyOne(decodePlpRequests(this.cfg, r), 'supply/withdraw-requested'),
936
+ plpCancel: (r: DecodableTransactionResult) =>
937
+ exactlyOne(decodePlpCancels(this.cfg, r), 'RequestCancelled'),
938
+ builderCode: (r: DecodableTransactionResult) =>
939
+ exactlyOne(decodeBuilderCodeSets(this.cfg, r), 'BuilderCodeSet'),
940
+ };
941
+ }