@parchment-trade/sdk 0.0.0-stage → 1.0.1

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 (95) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +193 -2
  3. package/REFERENCE.md +338 -0
  4. package/SECURITY.md +25 -0
  5. package/dist/general/providers/index.d.ts +2 -0
  6. package/dist/general/providers/index.js +2 -0
  7. package/dist/general/providers/parchment.d.ts +1 -0
  8. package/dist/general/providers/parchment.js +1 -0
  9. package/dist/general/providers/parchmentSdk.d.ts +23 -0
  10. package/dist/general/providers/parchmentSdk.js +11 -0
  11. package/dist/general/types/configTypes.d.ts +62 -0
  12. package/dist/general/types/configTypes.js +1 -0
  13. package/dist/general/types/index.d.ts +1 -0
  14. package/dist/general/types/index.js +1 -0
  15. package/dist/general/utils/address.d.ts +49 -0
  16. package/dist/general/utils/address.js +110 -0
  17. package/dist/general/utils/caller.d.ts +58 -0
  18. package/dist/general/utils/caller.js +182 -0
  19. package/dist/general/utils/errors.d.ts +16 -0
  20. package/dist/general/utils/errors.js +26 -0
  21. package/dist/general/utils/index.d.ts +4 -0
  22. package/dist/general/utils/index.js +4 -0
  23. package/dist/general/utils/jsBigInt.d.ts +26 -0
  24. package/dist/general/utils/jsBigInt.js +74 -0
  25. package/dist/general/utils/txShape.d.ts +8 -0
  26. package/dist/general/utils/txShape.js +39 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +4 -0
  29. package/dist/packages/index.d.ts +11 -0
  30. package/dist/packages/index.js +5 -0
  31. package/dist/packages/parchment/abortCodes.d.ts +196 -0
  32. package/dist/packages/parchment/abortCodes.js +522 -0
  33. package/dist/packages/parchment/batchClose.d.ts +268 -0
  34. package/dist/packages/parchment/batchClose.js +524 -0
  35. package/dist/packages/parchment/eventGuard.d.ts +106 -0
  36. package/dist/packages/parchment/eventGuard.js +119 -0
  37. package/dist/packages/parchment/gameCounterEvents.d.ts +59 -0
  38. package/dist/packages/parchment/gameCounterEvents.js +56 -0
  39. package/dist/packages/parchment/index.d.ts +52 -0
  40. package/dist/packages/parchment/index.js +30 -0
  41. package/dist/packages/parchment/liquidationCredit.d.ts +28 -0
  42. package/dist/packages/parchment/liquidationCredit.js +25 -0
  43. package/dist/packages/parchment/lockedMintEvents.d.ts +42 -0
  44. package/dist/packages/parchment/lockedMintEvents.js +46 -0
  45. package/dist/packages/parchment/monitoring.d.ts +369 -0
  46. package/dist/packages/parchment/monitoring.js +259 -0
  47. package/dist/packages/parchment/notes.d.ts +6 -0
  48. package/dist/packages/parchment/notes.js +1 -0
  49. package/dist/packages/parchment/onboarding.d.ts +50 -0
  50. package/dist/packages/parchment/onboarding.js +48 -0
  51. package/dist/packages/parchment/orderEffects.d.ts +207 -0
  52. package/dist/packages/parchment/orderEffects.js +268 -0
  53. package/dist/packages/parchment/orderParams.d.ts +72 -0
  54. package/dist/packages/parchment/orderParams.js +71 -0
  55. package/dist/packages/parchment/packageIds.d.ts +53 -0
  56. package/dist/packages/parchment/packageIds.js +149 -0
  57. package/dist/packages/parchment/parchment.d.ts +442 -0
  58. package/dist/packages/parchment/parchment.js +975 -0
  59. package/dist/packages/parchment/parchmentAccount.d.ts +189 -0
  60. package/dist/packages/parchment/parchmentAccount.js +273 -0
  61. package/dist/packages/parchment/parchmentMarket.d.ts +58 -0
  62. package/dist/packages/parchment/parchmentMarket.js +86 -0
  63. package/dist/packages/parchment/parchmentTypes.d.ts +2031 -0
  64. package/dist/packages/parchment/parchmentTypes.js +65 -0
  65. package/dist/packages/parchment/publicInts.d.ts +45 -0
  66. package/dist/packages/parchment/publicInts.js +383 -0
  67. package/dist/packages/parchment/reconcileEvents.d.ts +24 -0
  68. package/dist/packages/parchment/reconcileEvents.js +30 -0
  69. package/dist/packages/parchment/retainedFee.d.ts +43 -0
  70. package/dist/packages/parchment/retainedFee.js +68 -0
  71. package/dist/packages/parchment/sessionClose.d.ts +68 -0
  72. package/dist/packages/parchment/sessionClose.js +50 -0
  73. package/dist/packages/parchment/sessionKey.d.ts +78 -0
  74. package/dist/packages/parchment/sessionKey.js +154 -0
  75. package/dist/packages/parchment/slippage.d.ts +23 -0
  76. package/dist/packages/parchment/slippage.js +27 -0
  77. package/dist/packages/parchment/sponsor.d.ts +39 -0
  78. package/dist/packages/parchment/sponsor.js +48 -0
  79. package/dist/packages/parchment/stakingCalls.d.ts +33 -0
  80. package/dist/packages/parchment/stakingCalls.js +21 -0
  81. package/dist/packages/parchment/stakingEvents.d.ts +53 -0
  82. package/dist/packages/parchment/stakingEvents.js +71 -0
  83. package/dist/packages/parchment/triggerCalls.d.ts +46 -0
  84. package/dist/packages/parchment/triggerCalls.js +36 -0
  85. package/dist/packages/parchment/triggerEvents.d.ts +22 -0
  86. package/dist/packages/parchment/triggerEvents.js +33 -0
  87. package/dist/packages/parchment/triggers.d.ts +113 -0
  88. package/dist/packages/parchment/triggers.js +183 -0
  89. package/dist/packages/parchment/txBodies.d.ts +111 -0
  90. package/dist/packages/parchment/txBodies.js +683 -0
  91. package/dist/packages/parchment/txIntent.d.ts +99 -0
  92. package/dist/packages/parchment/txIntent.js +611 -0
  93. package/dist/types.d.ts +2 -0
  94. package/dist/types.js +1 -0
  95. package/package.json +58 -4
package/REFERENCE.md ADDED
@@ -0,0 +1,338 @@
1
+ # SDK reference
2
+
3
+ TypeScript client for the Parchment API. Pass a base URL. Calls go to `{baseUrl}/api/parchment`, and transaction builders return a `@mysten/sui` `Transaction`.
4
+
5
+ `@mysten/sui` is a peer dependency (`>=1.45.2 <3`): the app brings its own copy, so `ParchmentBuiltTx.tx` and the exported BCS structs are the app's own types. The SDK uses only `Transaction` (`from`/`build`), `bcs` and the `@mysten/sui/utils` helpers; its typecheck passes on both 1.45.2 and 2.36.0 (the dev dependency), and so do the tests except the `fitCloseBatch` ones, whose mock client has only the 2.x core API. The executed-transaction readers take either version's response type without a cast (1.x `SuiTransactionBlockResponse` with string event `bcs`, 2.x core results with bytes). `BatchCloseOptions.client` is whatever client `Transaction.build` takes in the app's version (`BatchCloseClient`); a 1.x client resolves the wallet coin with its own coin order.
6
+
7
+ Create a `ParchmentSdk`, then open the package with `sdk.Parchment()`. That call returns a new package client. `Parchment.create({ baseUrl })` returns the same kind of client directly. Reads go through `fetchApi`. Transaction builders go through `fetchApiTxObject`. `ParchmentAccount` and `ParchmentMarket` hold a fetched view and expose the same reads and builders for that view.
8
+
9
+ Reads are GET. Previews and transaction builders are POST JSON. Amounts are base units (USDC has 6 decimals, prices are 1e18 wad). Responses use a `{ "data": ... }` envelope. Every integer in the JSON, both ways, is a JavaScript BigInt literal string (bigint-serde `js_bigint`, e.g. `"123n"`): the client sends integers that way and returns every response integer as a `bigint` (`PUBLIC_INTS` lists where each response has them; a malformed one is `bad_body`). Exceptions: the staking `accRewardPerShare` and `rewardDebt` (u256) stay plain decimal strings, and cursors are opaque strings. Inputs still take a digit string or a bigint. The client does not send `sponsor` or `txKind`.
10
+
11
+ ```bash
12
+ npm test
13
+ npm run check
14
+ npm run test:package # npm pack, install the tarball in a scratch app, import it with plain Node
15
+ ```
16
+
17
+ Requires Node 22.14 or newer.
18
+
19
+ Inside this repo `exports` points at `./src/index.ts`: the frontend links the package (`file:`) and its bundler transpiles it. Node does not strip types under `node_modules`, so a packed copy ships compiled code instead: `npm pack` / `npm publish` run `prepack`, which builds `dist/` (ESM JavaScript and `.d.ts`, `npm run build`) and points `exports` at it for the tarball; `postpack` restores the source manifest.
20
+
21
+ ## Use
22
+
23
+ ```ts
24
+ import { ParchmentSdk, boundFromSlippage } from "@parchment-trade/sdk";
25
+
26
+ // Builders trust the API by default; pass txTrust for the optional strict checks (ParchmentOptions.txTrust).
27
+ const sdk = await ParchmentSdk.create({ baseUrl: "https://example.com" });
28
+ const parchment = sdk.Parchment();
29
+
30
+ const queue = await parchment.getQueue();
31
+ const mine = await parchment.getAccountQueue({ walletAddress: "0x..." });
32
+ const { markets } = await parchment.getMarkets();
33
+ const priceWad = markets[0]!.price.priceWad;
34
+ if (priceWad == null) throw new Error("no price");
35
+ const account = await parchment.getAccount({ walletAddress: "0x..." });
36
+ console.log(account.getPositions());
37
+
38
+ const referrals = await parchment.getReferrals({ walletAddress: "0x..." });
39
+ console.log(referrals.asReferrer.estimatedDirectBonus, referrals.estimate);
40
+
41
+ const harvest = await parchment.getHarvestTx({ walletAddress: "0x..." });
42
+ // Credits the exchange-side free balance, not a wallet coin. Default batch is 32. Hard max is 128.
43
+
44
+ const built = await parchment.getRequestOpenTx({
45
+ walletAddress: "0x...",
46
+ marketId: "0x...",
47
+ accountId: "0x...",
48
+ accountCapId: "0x...",
49
+ margin: "10000000",
50
+ leverage: markets[0]!.market.risk!.minLeverage, // within the market's minLeverage..=maxLeverage (launch minimum 50x or 100x)
51
+ isLong: true,
52
+ acceptablePriceWad: boundFromSlippage({ side: "long", action: "open", priceWad, slippageBps: 50 }),
53
+ });
54
+ // built.tx has that wallet address set as its sender. Set gas in the wallet, then sign.
55
+ ```
56
+
57
+ `baseUrl` is the API origin only. The client appends `/api/parchment`. Set `apiEndpoint` when the API is mounted on a path other than `api`.
58
+
59
+ The public host `https://api.parchment.trade` serves the parchment-api routes at its root (`https://api.parchment.trade/<X>` maps to parchment-api `/api/parchment/<X>`; the older `parchment-af-fe` is retired). This client always inserts `{apiEndpoint}/parchment` after `baseUrl`; nginx on api.parchment.trade also passes `/api/parchment/<X>` through unchanged, so `baseUrl: "https://api.parchment.trade"` (default `apiEndpoint` `api`) works.
60
+
61
+ ## Public API
62
+
63
+ | Method | Route |
64
+ |---|---|
65
+ | `getConfig` | `GET /config` |
66
+ | `getLimits` | `GET /limits` |
67
+ | `getMarkets`, `getMarket` | `GET /markets`, `GET /markets/{id}` |
68
+ | `getPriceHistory` | `GET /markets/{id}/prices` |
69
+ | `getCandles` | `GET /markets/{id}/candles` (`resolution` 1s / 5s / 15s / 1m / 5m / 15m / 1h / 4h / 1d; at most 500 candles, oldest first) |
70
+ | `getCandlesBatch` | `GET /markets/candles?markets=` (1 to 12 `marketIds`, repeats dropped; the same `resolution` / `fromMs` / `toMs` / `limit` for every market). `series` has one entry per market in request order: `{ marketId, candles }`, or `{ marketId, error: { code, message } }` for a market that failed alone. More than 12 is `too_many_markets` before any request. |
71
+ | `getPrices` | `GET /prices` |
72
+ | `getAccount` | `GET /accounts/{walletAddress}` (optional `limit`, `accountsCursor`, `positionsCursor`, `claimsCursor`) |
73
+ | `getPositions` | `GET /accounts/{walletAddress}/positions` (optional `cursor`, `limit`) |
74
+ | `getHistory` | `GET /accounts/{walletAddress}/history` |
75
+ | `getLp` | `GET /lp`: includes optional `sweep` (`ParchmentSweepParams`), null until Config is indexed. `floorBps`, `maxBps`, `intervalMs`, `capBps`, `cashFloor` (USDC base units, 6 dp), `stakerBps`, `treasuryBps`, `growthBps` decode to bigint. Missing on older services; use for display estimates, never transaction gates. |
76
+ | `getQueue` | `GET /queue` |
77
+ | `getAccountQueue` | `GET /accounts/{walletAddress}/queue` |
78
+ | `getReferrals` | `GET /referrals/{walletAddress}` |
79
+ | `getTradingReferral` | `GET /accounts/{walletAddress}/trading-referral` |
80
+ | `getBuilder` | `GET /builders/{code}` |
81
+ | `getAccountBuilder` | `GET /accounts/{walletAddress}/builder` |
82
+ | `getGameDefinitions` | `GET /game/definitions` |
83
+ | `getGame` | `GET /game/{walletAddress}` |
84
+ | `getLeaderboard` | `GET /leaderboards` |
85
+ | `getWhaleFeed`, `getRektFeed` | `GET /feed/whales`, `GET /feed/rekt` |
86
+ | `getPnlCard` | `GET /cards/pnl?positionId=` |
87
+ | `getProtocol` | `GET /protocol` |
88
+ | `getRecentPositions` | `GET /positions/recent` |
89
+ | `getMarketStats` | `GET /markets/stats` |
90
+ | `getLpQueue` | `GET /lp/queue` |
91
+ | `getMonitoring({ days? })`, `monitoringView` | `GET /stats/monitoring?days=N`: daily mint cap (room, gross requested, scale, burned excess, carry, flagged share), payout queue (entries, net owed, oldest age, daily history), per-market side exposure vs caps, fee split (retained, sweeps, vest penalty; coverage, staker share), two-step fill timing (price age, Lazer-to-storage, request-to-Lazer) and short-hold PnL. Integers are bigints; any missing key or bad literal is `bad_body` |
92
+ | `getStatsActivity({ days? })`, `statsActivityView` | `GET /stats/activity?days=N`: per market and UTC day the volume (notional opened plus notional closed or liquidated, as `markets/stats`), liquidated notional and count and two-step fills; collateral deposits vs withdrawals per day; each with its since-launch total (`allTime`), so a chart can draw the all-time running total under any window. Integers are bigints; any missing key or bad literal is `bad_body` |
93
+ | `getStakingHistory({ days })`, `stakingHistoryView` | `GET /stats/staking-history?days=7\|30\|90\|all`: the staking pool's realized APR (`aprWad`, 1e18 = 100%) and APY (`apyWad`, null when unrepresentable) over the window, `partial` / `annualized` flags, the rewards and average stake behind them, the PARCHMENT/USD quote used, and up to 500 pool snapshots (`tsMs`, `totalStaked`, cumulative `rewardsVestedUsdc` and `parchDripVested`, per-snapshot `parchmentUsdWad`), and, when the service serves it, `daily`: the exact per-UTC-day aggregate (rewards vested that day, stake time-weighted over every change, the day's quote and `aprWad`; absent from older services). Integers are bigints; any missing key or bad literal is `bad_body` |
94
+ | `getPreviewOpen`, `getPreviewClose` | `POST /previews/open`, `POST /previews/close` |
95
+ | `getOpenAccountTx` | `POST /transactions/open-account` |
96
+ | `getDepositTx`, `getWithdrawTx` | `POST /transactions/deposit`, `/withdraw` |
97
+ | `getRequestOpenTx`, `getRequestOpenAsDelegateTx` | `POST /transactions/request-open`, `/request-open-as-delegate` (two-step; see below) |
98
+ | `getRequestCloseTx`, `getRequestCloseAsDelegateTx` | `POST /transactions/request-close`, `/request-close-as-delegate` |
99
+ | `getCancelOrderTx`, `getCancelOrderAsDelegateTx`, `getExpireOrderTx` | `POST /transactions/cancel-order`, `/cancel-order-as-delegate`, `/expire-order` |
100
+ | `getEmergencyCloseTx`, `getEmergencyCloseAsDelegateTx` | `POST /transactions/emergency-close`, `/emergency-close-as-delegate` (optional `update`) |
101
+ | `getCloseRetiredTx`, `getCloseRetiredAsDelegateTx` | `POST /transactions/close-retired`, `/close-retired-as-delegate` (one-step close on a retired market; `positionId`, optional `update`) |
102
+ | `getOrders`, `getOrder`, `waitForOrder`, `requestedOrderIdFromTx`, `requestedOrdersFromTx` | `GET /accounts/{owner}/orders?status=pending\|final\|all&limit&cursor`, `GET /orders/{orderId}`; `waitForOrder` polls `getOrder`; the `requestedOrder*FromTx` helpers read the created order id from an executed request tx |
103
+ | `getLiquidateTx` | `POST /transactions/liquidate` (`/liquidate-retired` with `retired: true`). Keeper-only: the Move calls (`trade::liquidate` / `liquidate_retired`) abort `EKeeperOnlyLiquidation` (trade 36) unless the sender is an order keeper. Account-free: no `accountId`, no cap; the bounty goes to the keeper. Anyone else liquidates through `trade::flag_liquidatable` and then, once the position has stayed liquidatable for `liq_grace_ms` (30 s by default), `trade::liquidate_after_grace`; the SDK and API have no builder for that path yet. |
104
+ | `getSweepTx`, `getSweepSurplusTx`, `getClaimPendingMintTx` | `POST /transactions/sweep` (`maxEntries` 1..=8, default 8; `trade::sweep` is order-keeper only, trade 36), `/sweep-surplus` (the service re-prices every market with open interest into a `ReserveCheck` in the same PTB; there is no resume transaction: fee claims are never paused by pool coverage), `/claim-pending-mint` (owner cap; mints against basis parked by account-free liquidations) |
105
+ | `getHarvestTx` | `POST /transactions/harvest` (default batch 32, allowed 1 to 128) |
106
+ | `getBindReferrerTx` | `POST /transactions/bind-referrer` (the referrer cannot be the zero address: `0x0` / all-zero hex is a local `zero_referrer` (af-fe answers the same 400), chain `staking::EZeroReferrer` 13) |
107
+ | `getBindTradingReferrerTx` | `POST /transactions/bind-trading-referrer` (the referrer cannot be the zero address: local `zero_referrer`, chain `referral::EZeroReferrer` 5) |
108
+ | `getStakeTx`, `getUnstakeTx`, `getClaimTx` | `POST /transactions/stake`, `/unstake`, `/claim` (each PTB passes the `0x6` clock) |
109
+ | `getGrantDelegateTx`, `getRevokeDelegateTx` | `POST /transactions/grant-delegate`, `/revoke-delegate` |
110
+ | `getRegisterBuilderTx` | `POST /transactions/register-builder` |
111
+ | `getSetBuilderPayoutTx` | `POST /transactions/set-builder-payout` |
112
+ | `getInstallTreasuryTx` | `POST /transactions/install-treasury` |
113
+ | `getAuthRegisterTx` | `POST /transactions/auth-register` (admin; `parchment_trade::auth::register<C>(config, exchange, admin_cap)`, once per exchange) |
114
+ | `getGameAttachTx` | `POST /transactions/game-attach` (admin; `parchment_trade::game_book::attach<C>(config, exchange, admin_cap)`, once per exchange after `auth-register`; same body) |
115
+ | `getClaimDailyTx`, `getOpenCrateTx`, `getOpenCratesTx`, `getClaimQuestTx`, `getCollectMintTx` | `POST /transactions/claim-daily`, `/open-crate`, `/open-crates`, `/claim-quest`, `/collect-mint` |
116
+
117
+ Package split: `trade`, `order` (the two-step `request_open` / `request_close` / `cancel` / `expire` / `emergency_close` entries; `OrderRequest` type strings use `parchmentTradeTypePackageId`), the `oracle` receipt entries, `game`, `game_book` (incl. the admin `game_book::attach`, `getGameAttachTx`), `game_events`, `referral`, `builder`, `fees`, `fee_mint`, `launch`, `keeper` and `auth` are in the `parchment_trade` package; `access`, `account`, `authority`, `builder_book`, `config`, `events`, `exchange`, `liq_book`, `lp`, `market`, `position`, `referral_book`, `safety` and `timelock` stay in core. `getConfig()` returns `tradePackageId` (latest, the Move call target) and `tradePackageIds` (original first) next to `packageId`. `parchmentCallPackageId(config, module)` picks the package for a call target and `parchmentTradeTypePackageId(config)` the original id for trade type strings such as `oracle::PriceReceipt` or `fees::StakingSink`; event types stay under the core package (`parchment::events`) except the 12 game events, which are `parchment_trade::game_events::Event<...::game_events::XEventV1>` types under the original trade id (`parchmentTradeTypePackageId`). After publishing, an admin runs `getAuthRegisterTx({ walletAddress, authorityCapId })` once per exchange before trading.
118
+
119
+ Request objects use `walletAddress`. The JSON body still sends that address as `sender`, which is the API field.
120
+
121
+ Deposit, withdraw, request-open, request-close, cancel-order, emergency-close, grant-delegate, and revoke-delegate take `accountCapId`, the owned AccountCap object id, and send it on the JSON body. Withdraw requires the account cap. Open-account does not take one. Liquidate does not take one. A new cap can be passed with its version and digest.
122
+
123
+ `getGrantDelegateTx` takes `delegate` and `expiryMs`. Its legacy `nowMs` input is optional, deprecated and ignored; it is omitted from the request. The API-built `exchange::grant_delegate<C>` call (it also takes the shared Exchange) uses the shared Clock (`0x6`) in place of the old `now_ms: u64` argument. `getPreviewClose` keeps its separate optional `nowMs` simulation input.
124
+
125
+ `getQueue()` returns `{ length, totalOwed, lpCash, instantlyPayable }`. `getAccountQueue({ walletAddress })` returns the service shape `{ owner, hasClaim, net, rank }`. Amounts are base units (bigint). `lpCash`, `instantlyPayable`, and `rank` may be null.
126
+
127
+ `getAccount` returns a `ParchmentAccount`. Its `getPositions()` reads the fetched view and does not call the network again. Deposit, withdraw, request-open (and as-delegate), request-close (and as-delegate), cancel-order, liquidate, referral, trading-referral bind, stake, unstake, claim, daily drop, crate, quest, collect mint, and harvest are also on the account. When the owner has one account, that id is used; otherwise pass `accountId`.
128
+
129
+ `getMarket` returns a `ParchmentMarket`. `price.spreadWad` is the one-step spread (emergency / retired closes); two-step fills pay none. It is null while `price.status` is `price_unavailable` (the service has not read the market's pinned `PriceFeedStorage`).
130
+
131
+ A market the service indexed but that is missing from the on-chain Registry's `market_ids` (the contract cannot remove one today, and still accepts requests on a status-1 market) stays in `getMarkets` / `getMarket` with `delisted: true` (`marketIsDelisted`; absent on older services, read as false). The service keeps its rows and history and stops polling its storage (its price can go stale); the keeper still serves its open positions.
132
+
133
+ `getLiquidateTx` takes `update`, and `getEmergencyCloseTx` / `getEmergencyCloseAsDelegateTx` take an optional `update`: base64 or raw bytes of a signed Pyth Lazer update. The client base64-encodes bytes and sends `updateBase64`. It does not fetch an update itself; when an emergency close omits it, the service fetches the latest update for the market.
134
+
135
+ ### Two-step orders (keeper-filled)
136
+
137
+ Opens and closes are two transactions. The single-step `open` / `close` builders (and their `-as-delegate` forms) are gone.
138
+
139
+ 1. **Request.** `getRequestOpenTx` / `getRequestCloseTx` (or the `AsDelegate` forms) build `order::request_open` / `request_close`. No oracle update is needed. An open request escrows margin plus the execution fee ($0.02 at launch, `execFee` on the preview) from free balance; a close request escrows the free part of the execution fee and owes the rest (`feeOwed`); owner requests can add `topUp`, a USDC coin ref `{ objectId, version, digest }` from the wallet deposited in the same PTB. `acceptablePriceWad` is required for opens and must be non-zero (omitted, empty or `0` is rejected locally as `price_bound_required`, as the public API refuses it); on a close it may be `0` or omitted, no bound. The contract stores the bound but does not enforce it on two-step fills (free-option update 2026-10-19). Minimum margin is $0.90 after the open fee (`risk.minMargin` per market). At most `max_pending_orders` open requests can be pending per account (default 3; an admin sets it with `config::set_max_pending_orders`, bounds 1 to 32); close requests are never counted or capped. One more open request aborts `order::ETooManyOrders` (11). `getConfig().maxPendingOrders` serves the limit (bigint; the service falls back to 3 until indexed) and each `getAccount()` record's `pendingOpenOrders` (bigint) the live pending-open count (`pendingOrders` counts opens and closes and is display only); `hasPendingOrderSlot(maxPendingOrders, pendingOpenOrders)` mirrors the on-chain opens-only `pending_open_orders < max_pending_orders` check and returns null on older services that omit either field. Use it for open requests only; never gate a close on it. The SDK does not cache `getConfig()`; an app that caches it should, on an `ETooManyOrders` (order 11) abort, drop the cached config and refetch it and the account, because the admin may have lowered the limit (lead decision, 2026-10-19). Open requests take an optional numeric `builderId` or a registered `builderCode` (1 to 32 bytes of printable UTF-8, resolved to its id by the service), but not both.
140
+ 2. **Keeper fill.** A keeper fills the order at the plain oracle mid (no spread) with a signed price published after the request, inside the market's signed fill window (`signedWindowMs`, 1 s on every market at launch; the keeper takes the first such price), and by the fill deadline (`fillDeadlineMs`, 10 s after the request at launch). `fillWindowMs` (5 s at launch) is the request-side window that bounds the deadline and cancel delay; a fill does not read it. The fill executes at the eligible oracle mid; `acceptablePriceWad` is not enforced (there is no slippage protection on two-step fills). The fill is checked against the risk limits; on failure the escrow is refunded (`status: "refunded"`, `reason` one of `paused`, `killed`, `closed`, `fee`, `risk`, `session`, `retry`, `revoked` (`band`, outcome 12, is reserved: it decodes on old records but is never emitted since P21); a `paused` / `killed` / `session` / `retry` / `revoked` refund also returns the execution fee; `fee` and `risk` apply to opens only, `session` (outcome 13) is an equity session-end refund, `retry` (outcome 14) a liquidation backlog that one fill could not drain (nothing is paused; try again), and a close refund is 0). Anyone can expire it (`getExpireOrderTx`) strictly after `orderExpiresAfterMs(order)`: `fillDeadlineMs` for opens; for closes, `emergencyAfterMs` plus the fill-deadline span (`fillDeadlineMs - requestedMs`, not the fill window), so emergency close gets its turn first: at launch an open expires 10 s and a close 25 s after the request. Emergency close takes no exit bound and does not check the close request's stored `acceptablePriceWad`.
141
+ 3. **Cancel.** `getCancelOrderTx` (owner) or `getCancelOrderAsDelegateTx` (granted delegate) works at or after `cancelAfterMs` (the chain checks `now >= cancelAfterMs`; expiry, by contrast, is strictly after). The cancel delay is per market (7 s at launch; admins set it with `set_order_params`, and the chain floors it at 5 s, `order::MIN_CANCEL_DELAY_MS`). An open's margin escrow is refunded; a close escrows no margin, so its refund is 0 (its escrowed execution fee is kept) and the position stays open. The order finalizes with outcome 2 (`cancelled`).
142
+ 4. **Emergency close.** If a close request is still pending after `emergencyAfterMs` (15 s after the request at launch), the owner (`getEmergencyCloseTx`) or a granted delegate (`getEmergencyCloseAsDelegateTx`) can close it at the one-step spread price (own signed update, or omit it and the service fetches one). The update must be published strictly after `emergencyAfterMs`, otherwise `order::EEmergencyTooEarly` (10). There is no price bound on emergency close: no `acceptablePriceWad`, `acceptableEntryWad` or `acceptableExitWad` (the body types and builders do not carry them, and the builders drop them if an untyped caller passes one); the request's stored `acceptablePriceWad` is not checked either, so the exit executes at the one-step price whatever it is. If a hand-built body does send one, the two servers differ: parchment-service ignores it (no validation, same transaction), while af-fe returns a 400 for any of the three so a client cannot believe it set a new bound. The order finalizes with outcome 11 (`emergency_closed`). Every close has an emergency close: a market's timer is 5_000..=60_000 ms and at least the fill deadline plus 5 s, and `set_order_params` rejects 0 and u64::MAX (`market::EBadEmergencyAfter`). u64::MAX is a legacy sentinel: open orders carry it (they have no emergency close) and old records may show it.
143
+
144
+ The execution fee is refunded with the full escrow on outcomes 5 (`paused`), 6 (`killed`), 13 (`session`) and 14 (`retry`), whether from a keeper fill or an expiry. A plain expiry (outcome 3, `expired`) also refunds it unless the market's latest stored price at expiry falls inside the order's signed fill window (a price the keeper could have filled on, so the fee is kept); `execFeeRefunded` on the order row says which. Fills, liquidations, cancels, emergency closes and the other refunds keep it. Previews return `suggestedAcceptablePriceWad`, `maxFeeBps`, `execFee`, `fillWindowMs` and `cancelDelayMs`; the close preview also returns `emergencyAfterMs` and `feeOwed` (0 when free balance covers the exec fee, else the shortfall: a close escrows what is free, owes the rest and pays it from the close payout on a fill, so a close never fails `insufficient_free_balance` for the exec fee). Order rows carry `feeOwed` and `execFeeRefunded` (the escrowed exec fee returned; null while pending). All of them are bigints. The close preview's `emergencyAfterMs` is a delay (not an absolute time), exact up to u64::MAX; test it with `isEmergencyAfterDisabled`. `fillWad` (and `entryWad` / `exitWad`) is the oracle mid, the price a keeper fill pays. `oneStepWad` is the one-step spread price (mid + spread for long opens and short closes, else mid − spread) that emergency close and `close_retired` pay, null when the price is not live. `oneStepSpreadBps` is that spread floor; it is also on every market view (`market.oneStepSpreadBps`). Older services omit these two fields. Pass optional `slippageBps` (and `acceptableEntryWad` / `acceptableExitWad`) to a preview to get `suggestedBoundWad` next to the current `fillWad`. Both are stored-price suggestions only and do not constrain execution: fills ignore the acceptable price, so a crossed bound is not a preview error (older services returned `entry_price_worse` / `exit_price_worse`); only a malformed bound errors. A preview `slippageBps` above 10000 throws `bad_request` before any request, like `boundFromSlippage` and the service (0..=10000). A preview also takes `acceptablePriceWad`, the request bodies' name, as an alias: the SDK sends it as `acceptableEntryWad` (open) or `acceptableExitWad` (close), so older services read it too, and the specific key wins when both are set. A malformed or above-u128 preview bound throws `bad_price_bound` before any request, like the request bodies. The close preview's `pnl` (`ParchmentPnl`) types every field B serves: `loss`, `raw` → `trimmed` → `adjusted`, `winFee`, `closeToll`, `net`, `closeTollFromProfit` / `closeTollFromMargin`, `userProfit`, `marginReturned`, `payout` (= `payoutBeforeRefund` + `refereeRefund`), `feesCollectedNow`, `paidNow` / `queued` (profit paid at close vs. queued with its fees collected at harvest), `trimMaxShareBps`, `trimCPpm`; the fields after `net` are optional in the type for older services. The open preview also takes `accountId` and `minFactorBps`. Without `accountId` the service skips every account check: the referral-discounted open fee, the exec-fee / free-balance check (`insufficient_free_balance`), the account notional cap and `account_not_found`; `ParchmentAccount.getPreviewOpen` fills in the owner's account id when there is exactly one (an explicit `accountId` wins). `minFactorBps` adds `factor_worse` when the payout factor is below it. A preview's optional `topUp` (the wallet top-up for the exec-fee check, so only read with `accountId` on the open preview) is a base-unit integer (`"250000"` or a bigint, sent as `"250000n"`), not the coin ref the request transactions take; anything else (decimal, negative, empty, hex, an object) throws `invalid_amount` before any request, matching the service's 400.
145
+
146
+ ```ts
147
+ import { requestedOrderIdFromTx } from "@parchment-trade/sdk";
148
+
149
+ const built = await parchment.getRequestOpenTx({ /* as above */ });
150
+ // @mysten/sui 2.x: execute with `include: { events: true }` (or effects + objectTypes).
151
+ const result = await client.signAndExecuteTransaction({ transaction: built.tx, signer, include: { events: true } });
152
+ const orderId = requestedOrderIdFromTx(result);
153
+ if (!orderId) throw new Error("no order in this transaction");
154
+ const order = await parchment.waitForOrder(orderId, { timeoutMs: 10_000, pollMs: 500, signal });
155
+ if (order.status === "filled") console.log(order.positionId, order.fillPriceWad);
156
+ else if (order.status === "refunded") console.log("refunded:", order.reason);
157
+ ```
158
+
159
+ `requestedOrdersFromTx(result)` returns `{ orderId, kind }[]` for every order a request transaction created. `requestedOrderIdFromTx(result)` returns the id when there is exactly one, and null otherwise. They read `OrderRequestedEventV1`: the BCS when the result has it (bytes on 2.x; a base64 or base58 string per `bcsEncoding` on 1.x JSON-RPC; the order id is the first 32 bytes, `kind` is byte 160), and otherwise the JSON (`{ pos0: { order_id, kind } }` on JSON-RPC). With no events in the result, they fall back to the created `…::order::OrderRequest<…>` object, from 2.x `effects.changedObjects` plus `objectTypes` or 1.x `objectChanges`; `kind` is then null. They accept a 2.x `{ $kind: "Transaction", Transaction }` result, a bare 2.x transaction, or a 1.x JSON-RPC response. A failed transaction, or a result fetched without events or object types, gives `[]` / null. In that case, use the digest: every `Order` has `requestDigest`, so `getOrders(owner, { status: "all" })` can be matched on it once the indexer catches up.
160
+
161
+ `waitForOrder(orderId, { timeoutMs = 10_000, pollMs = 500, signal })` polls `getOrder` until `status` is no longer `pending` and rejects with an `Error` on timeout. Each read gets an abort signal that fires at the deadline, so a hung request cannot run past `timeoutMs`. Pass `signal` (for example from an `AbortController` tied to a component unmount) to stop early; the promise then rejects with the signal's reason. Read errors while polling (for example a 404 before the indexer catches up) are retried. After an error that carried `Retry-After` (a 429 from the rate limiter, or a 503/504 passed through by af-fe), the next poll waits at least that long, still capped by the deadline.
162
+
163
+ ### Paged reads
164
+
165
+ Positions, account lists (accounts, positions, queue claims), referrals (direct, second-level), orders and history are keyset pages; the service's default page is 50. A non-null next cursor means there is more: follow `nextCursor` (positions, orders, history), `accountsNextCursor` / `positionsNextCursor` / `queue.claimsNextCursor` (`getAccount`), or `asReferrer.directNextCursor` / `secondLevelNextCursor` (`getReferrals`, passed back as `directCursor` / `secondLevelCursor`). Do not total exposure from one page. A `null` or blank cursor is treated as omitted, so `cursor: page.nextCursor` is safe; a malformed one throws `ParchmentApiError` `bad_request` before any request (also a malformed address cursor, `accountsCursor` / `directCursor` / `secondLevelCursor`, and the history cursor, which now takes `null` like the others). `limit` must be a positive integer on every list route: a non-numeric, negative, zero, decimal or hex value (or a fractional or unsafe number) throws `ParchmentApiError` `bad_request` with no request, the 400 B r37 serves for a malformed `?limit=` (was `invalid_amount`). Any larger all-digit value is sent and the service clamps it to its `max-page-limit` (default 200); a blank string is omitted. History pages use the service's limit+1 pattern like its other pages (B r29 cc33cbf), so an exactly full last page returns `nextCursor: null`; an older service may still return a non-null `nextCursor` there whose next page is empty, which is harmless to follow. `ParchmentAccount.refresh()` / `refreshPositions()` read the first page only. Older services omit the next-cursor fields. `getBuilder` rejects `.`, `..`, whitespace and `/ \ ? # %` in a code locally (`bad_request`), like af-fe. `waitForOrder` replaces a `pollMs` that is 0, negative or not finite with the default 500.
166
+
167
+ ### Request timeouts and cancellation (opt-in)
168
+
169
+ By default no request has a timeout: a call waits as long as `fetch` does. To bound requests, pass `timeoutMs` and/or `signal` when you create the client, or scope them with `withRequestOptions`, which returns a new client and leaves the original unchanged (a field you leave out or set to `undefined` keeps the client's value; an explicit `null` clears it, e.g. `withRequestOptions({ signal: null })` drops an inherited signal, Round 40; from plain JS, `Parchment.create({ ..., signal: null, timeoutMs: null })` means none):
170
+
171
+ ```ts
172
+ const parchment = await Parchment.create({ baseUrl, timeoutMs: 15_000 }); // every request
173
+ const order = await parchment.withRequestOptions({ timeoutMs: 5_000, signal }).getOrder(orderId); // one call
174
+ ```
175
+
176
+ - `timeoutMs` applies to each HTTP request separately, including reading the body. When it passes, the request is aborted and rejects with `ParchmentApiError` `status 0`, `code "timeout"`. It must be a positive number (anything else rejects locally with `bad_request`); values above 2^31-1 ms are clamped.
177
+ - When `signal` aborts, the request rejects with the signal's reason, like `fetch` (an already-aborted signal rejects before any request is sent). A client-wide signal also stops `waitForOrder`.
178
+ - Inside `waitForOrder`, a per-read `timeoutMs` counts as a read error and is retried until the wait's own `timeoutMs`.
179
+ - An error response with a valid `Retry-After` (delta-seconds or an IMF-fixdate HTTP date such as `Wed, 21 Oct 2015 07:28:00 GMT`; other forms are ignored) sets `ParchmentApiError.retryAfterMs`; it is `undefined` otherwise.
180
+ - Transaction bodies check u64 fields (`margin`, `leverage`, `minFactorBps`, `positionId`, `amount`, `expiryMs`, `nowMs`, object versions) against u64::MAX and `acceptablePriceWad` against u128::MAX locally with no request. An overflow or a non-digit value throws B's codes (Rounds 50/53): `bad_amount` for a u64 field (`u64_of`) and `bad_price_bound` for `acceptablePriceWad` (`price_bound_of`). On request-open an omitted, empty or `0` `acceptablePriceWad` is `price_bound_required` (the public API needs a non-zero open bound); on request-close an omitted or empty one is not sent (B reads it as 0, no bound; Round 70, F-CLIENTS-2). Previews keep `invalid_amount`.
181
+ - A malformed address (path, body, `walletAddress`, ids) throws `ParchmentApiError` `bad_request` before any request (it was `invalid_address`), the code parchment-service and af-fe answer.
182
+ - A timed-out transaction-builder request moved no funds: builders only return unsigned bytes. A timeout after you submit a signed transaction to your Sui RPC is outside the SDK; check the digest before re-sending.
183
+
184
+ `decodeOrderRequestedEvent(bytes)` / `decodeOrderFinalizedEvent(bytes)` decode the order events with the full `events.move` BCS layouts (`ORDER_REQUESTED_EVENT_BCS`, `ORDER_FINALIZED_EVENT_BCS`) and return null unless the bytes fit exactly, so a changed struct is never read at stale offsets; `requestedOrdersFromTx` then falls back to the name-keyed JSON.
185
+
186
+ `finalizedOrdersFromTx(result)` returns `{ orderId, kind, outcome, status, reason, positionId, priceWad, priceTsMs, refund, execFee, execFeeRefunded }[]` for every `OrderFinalizedEventV1` in an executed fill, cancel, expire or emergency-close transaction (BCS when it fits exactly, otherwise the JSON). `orderOutcome(code)` / `ORDER_OUTCOMES` map the `order.move` `OUTCOME_*` codes 0-3 and 5-14 to the service's `status` / `reason`: 0 filled, 1 liquidated (on a close, the keeper fill found the position liquidatable and liquidated it), 2 cancelled, 3 expired (`execFeeRefunded` is the exec fee returned: an expiry refunds it unless the storage's latest write is inside the fill window, a keeper miss), 5-9 refunded with reason paused / killed / closed / fee / risk, 10 void (the position was already gone), 11 emergency_closed, 12 refunded with reason band (reserved: never emitted since P21), 13 refunded with reason session (`OUTCOME_SESSION_CLOSED`: filled while the equity market's session close was pending, or a close whose position the session force close already settled; escrow and exec fee refunded), 14 refunded with reason retry (`OUTCOME_REFUND_LIQ_PAUSED`, emitted by no pause despite its name: the account's liquidation backlog was over the per-fill cleanup cap, or its cleanup is on a killed market; escrow and exec fee refunded, and a later try succeeds). The retired outcome 4 (the old price refund) and any unrecognized code decode as `unknown`. Outcome 9 (`risk`) also covers Track A's account notional-cap refund rung (lead decision (c), merged into A main at f547a37): an open fill that would push the account past `max_account_notional` finalizes as `OUTCOME_REFUND_RISK` (9, `refunded` / `risk`) instead of aborting `EAccountNotional`. Since A 0fcb54e (Round 23 F1) it also covers an open whose own post-fee, referral-adjusted notional exceeds the market's `max_tx_notional`: the fill refunds with outcome 9 instead of aborting `ETxNotionalCap` at every fill until expiry (notional shared with earlier fills in the same transaction still aborts). A `request_close` on a position that was liquidated but not yet reconciled aborts `EPositionNotFound` (order 16) before any fee, and a two-step close fill no longer re-checks the live min hold (`request_close` already enforced it).
187
+
188
+ **Outcome 12 is reserved: never emitted since P21 (no bid/ask anywhere).** The signed Lazer update carries price, exponent and its timestamp (confidence is optional). Two-step fills settle at that signed mid. One-step `quote` requires the signed price to equal the storage price. A close fill liquidates when its fill price (the signed Lazer mid verified in the fill) has crossed bust, otherwise it fills at that plain mid. `OUTCOME_REFUND_BAND` (12) keeps its number so no code is reused; trade 26 (`EPriceOutsideSignedBand`) and oracle 18 (`EBadSignedRange`) are reserved and unnamed (`TRADE_RESERVED_ABORT_CODES`, `ORACLE_RESERVED_ABORT_CODES`).
189
+
190
+ **Pauses: liquidation and exits run under every pause; only the oracle kill switch stops them.** Each operation has its own rule. Opens: the exchange `paused`, the exchange-wide close pause, a market's `opensPaused` or `closesPaused`, a disabled market or `oracleKilled` make new open requests abort `order::ERequestsPaused` (15), and a pending open whose keeper fill hits any of them finalizes with outcome 5 (`paused`) or 6 (`killed`). Closes and liquidation: close requests, close fills (including liquidate-on-close), emergency close, `close-retired` (owner and delegate), the admin retired-market `emergency_close`, `liquidate`, `liquidate-retired` and `sweep` run under every pause, including `pause_all` and either close pause; only the market's `oracleKilled` stops them (`trade::EOracleKilled` 20). Under the kill switch close requests abort `order::ERequestsPaused` (15). A pending close whose keeper fill hits the kill switch refunds with outcome 6 before the liquidate-on-close branch. `trade::EClosesPaused` (22) is still declared on chain but not currently raised. Cancel and expiry run under every pause (decision 6); a pending order that expires under a pause or the kill switch refunds with outcome 5 / 6. Halted fills refund the full escrow including the execution fee (decision 5): outcomes 5 (`paused`), 6 (`killed`), 13 (`session`) and 14 (`retry`) return the fee with the escrow. The equity session-end arm and force-close are exempt from every pause (decision 3). `liquidationPaused(marketView)` returns true when the market's `oracleKilled` or the served per-market `liquidationPaused` (`/v1/markets`, `/v1/markets/{id}`, the close preview; the same kill switch) is set; null and missing count as not paused, and views can lag, so use it for copy and to skip pointless sends only. The close preview reports `liquidatable: false` only while the kill switch is on.
191
+
192
+ Per-market gates are on every market view (`market.market` on a `ParchmentMarket`): `status` (1n active, 2n reduce-only, 3n retired; `MARKET_STATUS_ACTIVE` / `MARKET_STATUS_REDUCE_ONLY` / `MARKET_STATUS_RETIRED`, `marketIsActive(view)`, `marketIsRetired(view)`; null when unindexed), `closesPaused` and `oracleKilled` (booleans, null when unindexed), and `timing.minHoldMs` (`TimingParamsV1`, a bigint; `timing` is null when unindexed). Read the hold with `marketMinHoldMs(view)`, which returns a number or null (unindexed or past `Number.MAX_SAFE_INTEGER`), and fall back to the launch default (10 s) yourself. A pending close keeps the hold that applied at request time.
193
+
194
+ Previews and the order tx routes read a market's order parameters from the service. To check them on chain, `decodeOrderParams(bytes)` decodes `market::OrderParamsV1` BCS (`ORDER_PARAMS_V1_BCS`, the return value of `market::order_params<C>(&Market<C>)` from a dev-inspect) into `{ fillWindowMs, fillDeadlineMs, cancelDelayMs, emergencyAfterMs, execFee, oneStepSpreadBps, signedWindowMs }` (seven u64 fields) as decimal strings. It returns null unless the bytes are exactly 56. `orderParamsFromJson(json)` reads the snake_case JSON rendering, with or without a `{ fields }` wrapper. `emergencyCloseDisabled(params)` is true when `emergencyAfterMs` is the legacy u64::MAX sentinel (a market cannot be set to it: `set_order_params` rejects it) (`EMERGENCY_CLOSE_DISABLED_MS`), and only for exactly that value as a decimal string or a bigint: a JSON number is never treated as disabled (it cannot hold u64::MAX exactly), and the i64::MAX clamp older services sent is not recognized. On an equity (market-hours) market the chain keeps `fillDeadlineMs <= ` the oracle price max age (`max_age_ms`): `set_order_params`, `set_oracle_params` and listing abort with the existing admin-only `market::EBadParam` (2) otherwise (A 4d17d14), so a decoded equity market's fill deadline never outlasts its price max age; always-open markets are unconstrained. No new abort code, outcome or field, and no user-built tx can hit it. Check an orders-API or preview `emergencyAfterMs` with `isEmergencyAfterDisabled(value)`, never `=== EMERGENCY_CLOSE_DISABLED_MS`: a disabled order compared that way looks enabled, and an `emergency_close` built from it aborts on chain.
195
+
196
+ `ORDER_TX_NOTE` is the note the service attaches to request-open / request-close bytes, the same string as the limits route's `orderTxNote`.
197
+
198
+ Order-module aborts use numbered `#[error(code = N)]` constants (0–17 and 19; 18 is reserved, never raised, and unnamed). `ORDER_ABORT_MESSAGES` maps constant names such as `ECancelTooEarly`, `ETooManyOrders`, `EInsufficientFree` or `ERequestsPaused` to user-facing text, and `ORDER_ABORT_NAMES` maps codes to names. `orderAbortMessage` accepts either the name (the service's `abort_name`) or the on-chain abort code as a number, bigint or decimal string, plain (`8`) or the raw clever-error u64; `decodeAbortCode` extracts N from the bitset (bits 48–55) and `orderAbortName(code)` returns the name. Round 52: `abortName(module, code)` and `abortMessage(module, nameOrCode)` also cover A's two new aborts, `market::ETxBelowMinNotional` (8, a listing or `set_risk_params` with `max_tx_notional` below `min_notional`) and `fee_mint::EWrongMintWitness` (2, a mint witness not from the reward coin's package), via `MARKET_ABORT_NAMES` / `MARKET_ABORT_MESSAGES` and `FEE_MINT_ABORT_NAMES` / `FEE_MINT_ABORT_MESSAGES` (same module/code pairs as the service's `abort_name`). They also accept `order` and `trade`. Round 53: `TRADE_ABORT_NAMES` names every `trade` abort (0–29, 31, 32, from A's `trade.move`, the same pairs as the service's `abort_name`; reserved 30 stays unnamed), so `abortName("trade", code)` matches the service and `abortMessage("trade", "EExitPriceWorse")` takes the service's name as well as the code (copy exists only for 31/32). Round 55: `EXCHANGE_ABORT_NAMES` (`parchment::exchange` 0–11, e.g. `ENoTreasury` 4) and `ACCESS_ABORT_NAMES` (`parchment::access` 0 `EUnregisteredWitness` and A r46's extension-timelock aborts 1 `EExtensionsSealed`, 2 `ENoExtensionProposal`, 3 `EExtensionNotReady`, 4 `ENotOnConfig`) give `abortName("exchange" | "access", code)` the same names as the service's `abort_name`; they are admin/keeper-side, so `abortMessage` has no copy for them. Other modules and codes return `undefined`. Round 56: `ACCESS_ABORT_NAMES` 5 `EAlreadyRegistered` (`timelock::propose_extension` for a witness already registered on that Config; admin-only, no copy). `DECLARED_NOT_RAISED_ABORT_CODES` lists the constants A keeps declared but never raises (numbers reserved): `trade` 0 `EBadVersion`, 10 `ELpInsolvent`, 16 `ENotAuthorized`, `order` 1 `ENotAuthorized` and `oracle` 0 `EBadVersion`; the trade/order names stay in the maps. Round 58: `ACCOUNT_ABORT_NAMES` names `parchment::account` 0–6 (`EBadVersion`, `EAccountExists`, `ENotAuthorized`, `EDelegateTooLong`, `EPositionMissing`, `EWrongExchange`, `ECapMismatch`; cap/delegate mismatches raise 2 / 6), so `abortName("account", code)` matches the service's `abort_name`; names only, no `abortMessage` copy. Round 61: `CONFIG_ABORT_NAMES` names `parchment::config` 0–9 and 100 (`EBadVersion`, `EBadFee`, `EFeeUnset`, `EBadBatch`, `EBadReferralFee`, `EBadSecondLevel`, `EBadRefereeDiscount`, `EBadEconParam`, A r51's `EBadExecFeeRecipient` 9 for `set_exec_fee_recipient(@0x0)`, and `EBadOrderParam` 100), for `abortName("config", code)`; admin setters only, names only, no copy. Round 64: `REFERRAL_ABORT_NAMES` (`parchment_trade::referral` 0–5), `BUILDER_ABORT_NAMES` (`parchment_trade::builder` 0, 2, 4–6 and the on-top codes 8–14: `EBadCharset`, `ECodeReserved`, `ETooManyCodes`, `EBadFee`, `ENotApproved`, `EAdminSuspended`, `ECreateFeeMismatch`; config 27 is `EBadBuilderFee`) and `STAKING_ABORT_NAMES` (`parchment_token::staking` 0–13) back `abortName("referral" | "builder" | "staking", code)`, including A r53's `@0x0` rejections: referral 5 and staking 13 `EZeroReferrer` (`bind_referrer` to `@0x0`), builder 5 `EZeroPayout` (payout `@0x0` on register / payout change) and builder 6 `EZeroOwner` (`register_builder` with owner `@0x0`); names only, no copy. Round 53 (B's codes are canonical): transaction-body builders now throw B's `tx.rs` codes for a malformed value too, not only on overflow: a non-digit u64 (margin, leverage, amount, positionId, versions, …) is `bad_amount`, a non-digit `acceptablePriceWad` is `bad_price_bound`, a sweep/harvest `maxEntries` outside its range is `bad_amount`, and a builder `feeBps` / `maxFeeBps` outside 0–100 (approvals 1–100, the 1% hard cap) is `bad_fee`. Preview bodies keep `invalid_amount`.
199
+
200
+ `ERequestsPaused` (order 15) is one code for several causes. To show which one, pass the market view you already have: `requestsPausedMessage(view, "open" | "close")` returns retired copy (pointing at close-retired), oracle-off copy, or reduce-only copy (opens only), and otherwise the generic `ORDER_ABORT_MESSAGES.ERequestsPaused` (also for a null view, unindexed state, a paused market or no `action`). It does no fetch and adds no field. The view can lag the chain, so use it for copy only.
201
+
202
+ Use `boundFromSlippage({ side: "long" | "short", action: "open" | "close", priceWad, slippageBps })` to compute `acceptablePriceWad` (stored on the request and emitted, but not enforced at fill: fills execute at the eligible oracle mid) as a WAD bigint: long open and short close round the ceiling `price * (1 + slip)` down; short open and long close round the floor `price * (1 - slip)` up. Both are the tight side and the same as the service preview's `suggestedBoundWad`, so the bound never allows more than `slippageBps`. A long close at 100% stays `0` (no bound); a short open floors at `1`, and so does a long open at price 0; an open always carries a bound. `side`/`action` other than these exact strings throw `invalid_amount`. A `slippageBps` above 10000 throws `bad_request`, like the service preview, which accepts 0..=10000.
203
+
204
+ `ParchmentBuiltTx` is `{ request, tx, commands, note, txBytesBase64 }`; `request` is the route and body the kind was built and checked for. `tx` is decoded with `Transaction.fromKind`, then `setSender`. The bytes are a transaction kind. They do not include gas. A kind with a command the service never builds is refused as `bad_tx` before it is returned: a framework (`0x1` / `0x2` / `0x3`) call other than `0x2::coin::zero`, a `TransferObjects` to anyone but the sender, a `SplitCoins` of the gas coin, a `MergeCoins`, a `MakeMoveVec`, a publish or an upgrade. `fitCloseBatch` / `batchCloseTx` refuse such a leg (`BatchCloseError`). By default (no `txTrust`, or `"unchecked"`), packages and arguments are not checked against the request: the builder API is trusted for them.
205
+
206
+ Transaction builders trust the API by default: without `txTrust` (or with `"unchecked"`, `UNCHECKED_TX`) they build without the intent checks. `txTrust` with pinned packages is the optional strict mode. `txTrust.packages` lists every approved version of each package the service's transactions call (`core`, `trade`, and for one-step closes, liquidations and sweeps `oracle`, `token`, `lazerIntegration`), taken from your own build, never from `GET /config`; add an upgrade's id only when you approve it. `base`, `game` and `mint` are required too: `getConfig()` is checked against them, because `parchmentCallPackageId` routes `parchment_base` calls and `parchmentMintTypePackageId` derives `MintCapKey` from those ids. `txTrust.triggerDelegate` pins the service's close-only delegate. `txTrust.heldDelegates(accountId, delegates)` should read from chain (not from the Parchment API) which of the named delegates hold a grant; `revoke-session-and-trigger` must then revoke exactly those. Without it that route must revoke every distinct named delegate, so revoke a single grant with `getRevokeDelegateTx`. A request-open with a `builderCode` and no `builderId` is refused under `txTrust`, because the service resolves the code through its own index and any id would pass: resolve the code to its id from a source you trust (your own registry or a chain read, not the Parchment API) and send `builderId`. Every builder response must then call only those packages, each with its own modules, and exactly the route's action as `parchment-service` builds it, with the request's account, cap, market, order, coin, amounts, side, bound and delegates (`txIntentViolation`); anything else is `ParchmentApiError` `intent_mismatch`, before the transaction reaches a signer. A Move call into another package is refused whatever it does inside, so a transfer hidden in a Move call cannot pass. A `SplitCoins` is refused except the one creation-fee split `create-builder` makes, so a response cannot skim a requested coin (stake, deposit, top-up) before the action takes it. Every route the SDK builds has an intent check: staking binds the staked coin and the unstake amount, referral binds the referrer (and account and cap), game claims and crates the account and cap (and crate count or quest id), builder routes the builder id, code, payout, fee and creation coin, admin routes the `AuthorityCap` and their arguments. `getConfig()` naming a core, trade, base, game or mint package id (singular or in the `*PackageIds` lists), or an oracle, token or Lazer id when those are pinned, outside `txTrust` is `untrusted_package`. Expire-order is bound to the request's account and order (and market when given); claim-pending-mint to the account and cap, with the mint consuming that claim's ticket; a one-step close's loss mint to the request's account and the close's ticket. One-step routes (emergency close, close-retired, liquidate, liquidate-retired, sweep) run `update_price_feed`, `adapter::quote` and `begin_price` once each, `begin_price` taking that quote for the request's market and the action taking that receipt. When the request carries `update` (liquidate and sweep always do), `update_price_feed` and `adapter::quote` must carry exactly those bytes. An emergency or retired close without `update` is refused under `txTrust`: the service would attach its own signed update, which the chain still verifies as a fresh Lazer signature, but which price within that freshness window would be the builder's choice. Pass the `update` you fetched, or set `txTrust.acceptServiceOracleUpdate: true` to take the service's.
207
+
208
+ `getExitEarlyTx` builds the legacy, unbounded `locked_mint::exit_early`, which exits every bucket eligible when it executes, so a wallet prompt left open across another bucket's unlock forfeits part of that bucket too. Use `getExitEarlyBoundedTx({ unlocks, maxPenalty })` with the game profile's `earlyExitUnlocks` and `earlyExitPenaltyNow` instead: `locked_mint::exit_early_bounded` exits only those entries and aborts above that penalty. One bound the SDK cannot add until the contracts do: `getRequestOpenTx` carries the margin but not the execution fee, which the chain reads when the request executes, so the fee-inclusive total shown before signing is not enforced. The SDK will build the bounded entry once the contracts add it.
209
+
210
+ `fitCloseBatch` / `batchCloseTx` take the same optional `txTrust`. With a trust, each leg needs its `request` (`ParchmentBuiltTx.request`: pass `{ tx: built.tx, request: built.request }`), must be a request-close or close-retired route for the batch's sender, and must pass `txIntentViolation` before it is copied in; the assembled batch must push and quote only updates the legs carried. Without one (or `"unchecked"`) the legs are held only to the command shapes. Either way, legs that quote the same feed share the first leg's signed update (separately fetched updates could otherwise revert the batch with `EOracleNotNewer`); under a trust, a leg whose request named a different update is refused instead of repriced.
211
+
212
+ `fitCloseBatch` sizes a Close All against `CLOSE_BATCH_BUDGET` (3/4 of Sui's 1,024 events). A retired close counts its own contract events (29: 26 from the close, mint and game hook, the 2 `CountersUpdatedEvent`s a close emits, one per `game_book` call, and the one `PositionsReconciledEventV1` of its reconcile prelude; contracts `close_event_budget_tests` pins the game hook's 12. Before the 2026-10-30 aggregate events it was 73: one event per counter kind and per reconciled position) plus, once per distinct account it settles, the game progression that close can unlock (`playerProgressionEvents`: each achievement and quest fires once per player, with their XP, cosmetics, tier-ups and crates). That bound is an exact maximum from the contract constants, so it takes its share of the 1,024 directly and the 3/4 margin applies to the legs' own counts (`CloseBatchUsage.progressionEvents` is the progression part of `events`). With no `gameLimits` it uses the registry maxima (`GAME_REGISTRY_MAXIMA`, 1,057 events per account, above Sui's 1,024): no retired close fits. With the live sizes one account fits 24 retired closes per transaction at 10 achievements / 10 quests / 5 cosmetics / 4 thresholds and 12 at half the maxima (32 and 16 with no margin on the legs' counts; 9 and 4 at 73 events per close). Pass the live sizes, `gameLimits: gameEventLimitsFromDefinitions(await parchment.getGameDefinitions())`, to batch more. A registry that grows between the fit and execution can still exceed it.
213
+
214
+ More positions than one tx holds go out as several txs. `planCloseBatches(legs, { sponsored, gameLimits, maxTrades })` groups positions by kind alone (`{ retired, account }`, nothing built, no network) into consecutive batches; build each batch just before it is sent (a retired close's signed update ages) and `fitCloseBatch` it: it may carry fewer than planned (tx bytes are not planned), and the rest joins the next batch. `splitCloseBatches(legs, options)` fits every batch of legs that are already built. With `sponsored`, a batch follows the sponsor's shapes (`SPONSOR_BATCH_BUDGET`): up to `maxTrades` (default 32) `request_close_as_delegate` legs, or up to `min(maxTrades, 24)` `close_retired_as_delegate` legs (`SPONSOR_BATCH_BUDGET.retiredTrades`, service `sponsor.max-retired-closes-per-tx`), never mixed. A sponsored retired batch pushes each market's signed update once (the sponsor refuses a second push of the same storage and feed); each close keeps its own quote and receipt. Put a market's retired closes next to each other. The sponsor bounds a batch by its simulated gas (`max-gas-budget` per request, `one-step-max-gas-budget` per retired close, at most 1 SUI), so after a `gas_budget_exceeded` refusal plan again with a smaller `maxTrades`. A sponsor older than retired batching refuses a batch of two or more (`too_many_trades` / `disallowed_command`); `maxTrades: 1` is its one-close shape.
215
+
216
+ `decodeCountersUpdatedEvent` decodes `parchment_game::game_events::Event<CountersUpdatedEvent>` (`COUNTERS_UPDATED_EVENT_BCS`: `player`, `account` or null, `kinds`, `values`: one event per `game_book` call, `kinds[i]` changed to `values[i]`, each kind at most once). `GAME_COUNTER_KIND` names the kinds (0 opens, 1 markets, 2 / 3 a genuine win's leverage and profit / margin bps, 4 win streak, 5 tier, 6 daily streak, 8 fee credit, 9 genuine closes, 10 genuine volume, 11 realized gain, 12 crates opened, 13 quests completed, 14 realized loss, 15 unopened crates, 16 fee credit used, 17 XP, 18 season XP, 19 daily volume, 20 daily volume XP, 21 daily win XP, 22 XP day, 23 season ID). Crate batches combine deferred catch-up and draws and always carry XP snapshots; other calls emit separately in a transaction (a close emits two), so `latestGameCounters` keeps the last value per player and kind from events in sequence order.
217
+
218
+ `decodePositionsReconciledEvent` decodes `parchment::events::Event<PositionsReconciledEventV1>` (`POSITIONS_RECONCILED_EVENT_BCS`: `account_id`, `market_id`, `position_ids`), one per reconcile call that removed swept or liquidated positions from an account. `decodePositionReconciledEvent` decodes the per-position `PositionReconciledEventV1 { account_id, position_id }`.
219
+
220
+ ### Gas sponsorship: trading session keys (optional)
221
+
222
+ Against `POST /api/parchment/sponsor`. The sponsor pays gas only for a session key's trades: the `*-as-delegate` kinds in `PARCHMENT_SPONSORED_ACTIONS` (`request-open-as-delegate`, `request-close-as-delegate`, `cancel-order-as-delegate`, `emergency-close-as-delegate`, `close-retired-as-delegate`) whose sender is a live delegate of the account. Owner (`AccountCap`) transactions are never sponsored; the wallet pays their gas.
223
+
224
+ ```ts
225
+ // Once: an Ed25519 key in the app, granted as a delegate by one wallet-signed, self-paid tx.
226
+ const session = createSessionKey(); // { address, keypair }
227
+ const grant = await account.getGrantDelegateTx({ accountCapId, delegate: session.address, expiryMs: sessionKeyExpiryMs() });
228
+ // ... wallet signs and executes grant.tx; persist sessionKeySecret(session) (never log it).
229
+
230
+ // Each trade: the session key is the sender; no wallet prompt.
231
+ const built = await account.getRequestOpenAsDelegateTx({ walletAddress: session.address, ... });
232
+ try {
233
+ const proof = await signSponsorProof(session, accountId, fromBase64(built.txBytesBase64)); // personal message, no wallet prompt
234
+ const sponsored = await parchment.sponsorTransaction({ walletAddress: session.address, txKindBase64: built.txBytesBase64, ...proof });
235
+ const config = await parchment.getConfig();
236
+ const packages = [config.packageId, ...(config.packageIds ?? []), config.tradePackageId, ...(config.tradePackageIds ?? []), config.oraclePackageId, config.tokenPackageId].filter((id) => id !== null);
237
+ const signature = await signSessionSponsoredTx(session, sponsored, fromBase64(built.txBytesBase64), packages);
238
+ // execute sponsored.txBytesBase64 with [signature, sponsored.sponsorSignature] within sponsored.validEpoch.
239
+ } catch (error) {
240
+ if (isSessionKeyRevoked(error)) forgetSessionKey(); // 403 not_delegate: revoked, expired or never granted
241
+ if (!shouldPayOwnGas(error)) throw error; // 400 paused / simulation_failed / disallowed_*: the trade would fail
242
+ // Wallet path: the owner's AccountCap transaction (getRequestOpenTx), user-paid gas.
243
+ }
244
+
245
+ // Revoke: one wallet-signed tx (runs under every pause), then delete the stored key.
246
+ const revoke = await account.getRevokeDelegateTx({ accountCapId, delegate: session.address });
247
+ ```
248
+
249
+ **What a session key can do.** On chain a delegate can only call `order::request_open_as_delegate`, `request_close_as_delegate`, `cancel_as_delegate`, `emergency_close_as_delegate` and `trade::close_retired_as_delegate`; every proceed and refund stays in the account's free balance. Withdraw, deposit, `grant_delegate` / `revoke_delegate`, the referrer and game / reward claims all need the `AccountCap`, which stays in the wallet. A leaked session key (for example a key in browser `localStorage` read by an XSS) can open and close trades at the owner's risk but cannot move funds out of the account; the owner cuts it off with `revoke-delegate`. A delegate expires at its configured future time; use `u64::MAX` for an effectively indefinite grant. `sessionKeyExpiryMs()` defaults to 30 days less an hour (`SESSION_KEY_TTL_MS`) and accepts longer durations.
250
+
251
+ **Take-profit and stop-loss.** One of each per position; each closes the whole position at the next price, like a normal close. `setTriggers({ owner, accountId, target, takeProfitWad, stopLossWad }, signer)` has the owner sign one proof for both legs (`null` clears a leg) and posts it; `target` is `{ positionId }`, or `{ openTxDigest, marketId, isLong, openTxBytesBase64 }` for TP/SL set with an open: sign the open transaction first, then the proof over that signed transaction's digest (`openTxBytesBase64` is the signed transaction's bytes, posted next to the proof but not signed; the service reads the open's epoch bound from it and refuses an open target with a leg set without it, and the SDK refuses, as `bad_request`, missing bytes with a leg set or bytes whose digest is not `openTxDigest`; clearing both legs needs none), wait for `setTriggers`, and only then send the open, so the legs are stored before the open can execute (the service attaches them to an open of that market and side when it fills; the page can close). 503 `price_unavailable` means the service has no current price for the market yet: retry in a moment. The proof (`triggerProofMessage`) is seven `\n`-joined lines, no trailing newline: `Parchment trigger v1`, owner, account id, `position <id>` or `open <tx digest> <market id> long|short`, `tp <price>` or `tp none`, `sl <price>` or `sl none`, expiry ms, with plain decimals; the body (`triggerRequestBody`) carries the same integers as js_bigint literals. Proofs apply in signing order, so an older one cannot be replayed over a newer one. `getTriggers({ walletAddress })` lists pending, armed, firing and submitted rows, each with `stopLossDropped` (`{ reason, ms, wad }`, present when a stop past the filled position's liquidation was not armed; such a row is listed even when idle, until the next set), `voidReason` (voids from the last seven days are listed), `lapsed` and `paused` (no close-only grant: the legs stay but nothing fires until it is granted again), and follows `nextCursor`; positions carry `takeProfitWad`/`stopLossWad` and a trigger's close in history carries `triggerKind`/`triggerPriceWad`. Triggers need the close-only delegate (`config.triggerDelegate`) in the account's `closeDelegates`: `getGrantCloseDelegateTx` grants it alone, `getGrantSessionAndTriggerTx` grants it with the browser session key in one transaction (both always grant the service's configured address and take none from the caller), and `getRevokeDelegateTx` (or `getRevokeSessionAndTriggerTx`, which revokes only the grants held) removes it. The close-only delegate can request and execute closes and nothing else, and it does not expire. `stopAgainstLiquidation(side, stop, liquidation, entry)` reports `past` when a long stop is at or under liquidation or a short stop at or over it, and `near` when the stop is in the last 20% of the entry-to-liquidation distance (`NEAR_LIQ_DISTANCE_FRACTION`), for any leverage including a 1x long's liquidation at 0; unknown inputs are `ok`.
252
+
253
+ `assertSessionTradeKind(kind, sessionAddress, packages)` refuses, before the key signs, any kind that is not a `*_as_delegate` trade: one that calls `withdraw`, `deposit`, `grant_delegate`, `revoke_delegate`, an owner entry, a function in a package outside `packages` (the app's Parchment package ids: core, trade, oracle and token, every version), publishes, splits / merges coins, builds a vector, or transfers anything to an address other than the session's own (the service hands back the zero coin `mint_on_loss` returns). `signSessionSponsoredTx` runs it and checks the sponsor's bytes are that exact kind from the session address with the sponsor paying gas (`isSponsoredCopyOf`). `liveSessionDelegate(account.delegates, address)` is the account's entry for the key while it is live with 10 minutes to spare, else null.
254
+
255
+ `sponsorTransaction` sends `{ sender, txKindBase64, senderSignature, expiresAtMs }` (`expiresAtMs` as a js_bigint string); the service reads the account from the kind. The session proof (`signSponsorProof(session, accountId, kind)`) is the session key's personal-message signature of `sponsorProofMessage`: five `\n`-joined lines, `Parchment session-key sponsorship v1`, `sender: <session address>`, `accountId: <account>`, `txKindSha256: <lowercase hex SHA-256 of the kind bytes>`, `expiresAtMs: <decimal>`, with addresses as `0x` + 64 lowercase hex digits. It expires 60 s ahead (`SPONSOR_PROOF_LIFETIME_MS`; the service refuses more than 120 s, `MAX_SPONSOR_PROOF_LIFETIME_MS`). A bad `walletAddress`, a missing proof or a missing kind is `bad_request`, and a kind that is empty or not standard base64 after trimming is `bad_tx_kind`, before any request; a 200 without `txBytesBase64` / `sponsorSignature` is `bad_tx`. Service errors keep their status, code and `retryAfterMs`. Limits are per account (shared by all its session keys). `shouldPayOwnGas` is true for 403 `not_delegate`, 401 `session_proof_expired` and 400 `bad_session_proof_expiry` (a browser clock too far off), every 429 (`in_flight_limit`, `account_rate_limited`, `request_rate_limited`, `account_failure_limit`, `rate_limited`), 400 `gas_budget_exceeded`, 502 / 503 / 504 and `network` / `timeout`: the session key signed nothing, and the wallet path is a different (owner) transaction. It is false for every other 400 (`paused`, `simulation_failed`, `disallowed_*`, `mixed_*`, `bad_account`, ...), 413 and local refusals.
256
+
257
+ ## Trading referral
258
+
259
+ `getTradingReferral({ walletAddress })` returns `{ referrer, refereeCount, totalEarned, pending }`. Amounts are base units (bigint). `referrer` is null when the wallet is unbound. `getBindTradingReferrerTx` requires `accountCapId` and `referrer`, and sends version and digest only when the caller provides them. It does not change `getBindReferrerTx`.
260
+
261
+ Accrued referral credits sit in the same free balance as other credits. Withdraw them with `getWithdrawTx`. The default split comes out of the taker fee: 10% direct, 2.5% second level, and a 5% referee discount.
262
+
263
+ ## Builder codes
264
+
265
+ Builder codes charge only an on-top fee: `feeBps` of the trader's profit, paid only by accounts that approved the code, never part of existing fees, with a 1% hard cap (`BUILDER_FEE_HARD_BPS`). `getBuilder({ code })` returns `{ id, code, payout, owner, active, status, selfServe, feeBps, revokeOpen, pending, totalEarned }` (`status` is `active`, `paused`, `suspended` or `tombstoned`). Amounts, ids and bps are bigints; `code`, `payout` and `owner` are null when the API serves none. `getOwnedBuilders({ walletAddress })` lists the codes a wallet owns and `getBuilderApprovals({ accountId })` an account's approvals (`builderId`, `maxFeeBps`). Owner self-service: `getCreateBuilderTx` (code 3-32 bytes of `[a-z0-9_-]`, payout, feeBps, a USDC coin of at least the live price `getConfig().builders.createFee`, default 1 USDC, which the service splits off), `getSetBuilderFeeTx`, `getPauseBuilderTx`, `getResumeBuilderTx`, `getTransferBuilderTx` (one transaction). The trader: `getApproveBuilderTx` (AccountCap, `maxFeeBps` 1-100) and `getRevokeBuilderApprovalTx`. The close preview's `pnl.builderFee` is already out of `userProfit` and `payout`. `getAccountBuilder({ walletAddress })` returns that object or null. `getSetBuilderCreateFeeTx` (admin) sets that price: `amount` in USDC base units, 0 (free) to `MAX_BUILDER_CREATE_FEE` (100 USDC), else a local `bad_amount`, then the authority cap. `getRegisterBuilderTx` (admin) requires the authority cap id, code, payout, owner, and `feeBps`, and sends version and digest only when the caller provides them. It checks fields in parchment-service's order: `code` (`bad_code`, 1-32 bytes) -> `feeBps` (`bad_fee`) -> `payout` -> `owner` (`bad_request`) -> authority cap; a missing `authorityCapId` (here, on `getAuthRegisterTx` and on `getInstallTreasuryTx`) is `authority_cap_required`. `getBuilder` with a NUL in the code or a code over 32 bytes throws `bad_code` before fetching (af-fe and parchment-service answer `GET /builders/{code}` the same way). Request-open and request-open-as-delegate take an optional numeric `builderId` (the registered builder id) or `builderCode` (the service resolves it, and an unregistered code fails with `unknown_builder`). Each is sent only when the caller provides it. Passing both is rejected client-side, because the service would silently prefer `builderId`. A `builderId` (here and in every builder action) is a u32, sent as a js_bigint (`"7n"`), the only form af-fe and the service accept; anything else is a local `bad_amount` (F-CLIENTS-2; it was `bad_request` since Round 49). On request-open an omitted, null or empty (`""`) `builderId` / `builderCode` means no builder, as in B and af-fe, and is not sent. A response whose address field is missing or malformed rejects with `bad_body`, not `bad_request`. A `getRegisterBuilderTx` `code` outside 1 to 32 bytes is a local `bad_code`, B's code. Chain accepts 1 to 32 arbitrary bytes. B r51 indexes the exact code bytes, but it takes codes as UTF-8 strings and refuses NUL: use printable UTF-8 (ASCII recommended); a non-UTF-8 or NUL-containing code cannot be resolved by the service, so the SDK rejects a NUL or a lone surrogate (`bad_code`; Round 67, and since Round 69 also for the request-open `builderCode`, which was `bad_request`). Edge whitespace, spaces, `%`, `/`, `?`, `#` and `\` are allowed, and `getBuilder` percent-encodes the code into the path (af-fe re-encodes it for the service). Only `.` and `..` are a local `bad_request` in `getBuilder`, because URL parsers resolve them as dot segments (the SDK used to reject the path characters too).
266
+
267
+ Builder rewards sit in the same free balance as other credits. Withdraw them with `getWithdrawTx`. Codes default off. A code's fee is on top of the existing fees, out of the trader's profit. A 0% code needs no approval; if the owner raises a fee above the trader's approval, it is not charged until the trader approves again.
268
+
269
+ ## Game
270
+
271
+ `getGameDefinitions()` lists quests, achievements (with `mintAmount`), cosmetics and `achievementMintFractionBps`. `getGame({ walletAddress })` reads one wallet, including `pendingMint`, `lastDailyDay`/`dailyClaimedToday`, `feeCredit`/`feeCreditUsed` and `feeAchievements` (`feesRequired`, `feesNeeded`, `eligibleMint`, `mintAtUnlock`, `unlocked`). Fee rungs are full-or-wait: `eligibleMint` and `mintAtUnlock` are the full `mintAmount` or 0, never a partial amount, `feesNeeded` counts the full cost of this rung and every earlier locked rung, and `unlockable: false` marks a rung no top-up can unlock under the current config (see `ParchmentFeeAchievement`). Daily claims, quests and crates grant XP or cosmetics only; FEES_PAID achievements collected with `getCollectMintTx` are the only game mint. Leaderboard rows and `viewer` carry `stats`; feed entries carry `side`, `isLong`, `leverage` and `kind`. These newer fields are optional in the types so older service builds still parse. `getLeaderboard({ period, by, walletAddress?, cursor?, limit?, nowMs? })` ranks by `pnl`, `pnlPct`, `volume`, `winStreak`, or `xp` over `daily`, `weekly`, `all`, or `season`. It is offset-paged like B's route: pass the previous page's `nextCursor` (a decimal row offset, `null` on the last page) as `cursor`; `limit` defaults to 50 and is capped at 200 by the service; `nowMs` (0..=9223372036854775807, i64::MAX) fixes the daily/weekly/season window, else the service uses now. A bad `nowMs` is a local `bad_time` (B's code, Round 49; it was `bad_request`) and a bad `limit` a local `bad_request` (B r37's code, Round 51; was `invalid_amount`), with no request; a bad `period`, `by` or `cursor` is a local `bad_period`, `bad_metric` or `bad_cursor`, the codes B serves, and `period`/`by` are trimmed like B. B sets `nextCursor` only while rows remain past `cursor + limit` (it counts the ranked total), so an exactly-full last page has `nextCursor: null`. `getWhaleFeed({ limit? })` lists whale opens (`kind: "open"`) and big closes (`"close"`); `getRektFeed({ limit? })` lists liquidations (`"liquidate"`). `limit` is B's `?limit` (default 50). `getPnlCard({ positionId })` reads one card; its `pnlPct` is basis points of margin (`"1234"` = 12.34%); an empty `positionId` is a local `position_required` and a non-integer one a local `bad_position`, B's codes, with no request; an all-digit id above u128::MAX is a local `bad_position`, as B r51 answers (400; Round 69, was a local `position_not_found` over 78 digits). A malformed close-preview `positionId` / `nowMs` is a local `bad_request` (B r51 `check_preview_ids`), and a request-open `builderCode` over 32 bytes or with a NUL is `bad_code` (B r51 `code_bytes`). Round 43: the history cursor must fit an i64 (else a local `bad_request`); since Round 55 it is B's `opt_i64` like `fromMs`/`toMs`, so a negative or `+`-signed cursor is sent unchanged and a padded one is a local `bad_request` (a null or blank cursor is still omitted) and the leaderboard cursor a u64 (else a local `bad_cursor`), price-history `fromMs`/`toMs` must parse as B's `opt_i64` (an optional `+`/`-` sign and digits within i64; negatives are sent), else a local `bad_request`, B's code (a non-digit was `invalid_amount`), all with no request. Account records may carry `pendingMintBasis`, `pendingMint`, `delegatesCount`; claims `remaining` (what is still owed) and `feeRemaining`; `getLpQueue().lpCash` is null until indexed. `getClaimDailyTx` and `getOpenCrateTx` omit `questId`. `getClaimQuestTx` sends it; `questId` is a u8 like B's: empty is a local `quest_required`, anything else outside 0 to 255 a local `bad_quest`. All three require `accountCapId` and send version and digest only when the caller provides them.
272
+
273
+ Daily claims grant streak XP (default base/step/max 100/10/300 drop units, each granted as 10 XP). Win streaks, realized-PnL rewards, quests and crates mint zero PARCHMENT; play rewards are XP, cosmetics and crates. Season grants record completion only, granting neither coins nor XP. Crates use on-chain randomness and have only XP/cosmetic outcomes. Legacy quest money, crate money bounds/weight and win-mint inputs must be zero.
274
+
275
+ Only fee-backed achievements accrue game PARCHMENT. Achievement kind 8 (`FEES_PAID`) uses cumulative retained fees in USDC base units as its threshold; only this kind may have nonzero `mint_amount`. The achievement mint fraction defaults to 2,500 bps (25%), with a maximum of 10,000. `getCollectMintTx` posts `collect-mint` with the same account cap as `getClaimDailyTx` to issue pending rewards. Default issuance caps are 5 PARCHMENT per wallet per UTC day and 1,000 globally per exchange; capped amounts remain pending. The core loss-based fee mint has no per-account time-window cap. Volume does not mint, and no method redeems PARCHMENT for collateral. `getInstallTreasuryTx` installs the single PARCHMENT treasury cap once, and sends version and digest only when the caller provides them.
276
+
277
+ The API builds the Move calls and the SDK decodes the returned transaction kind. These game endpoints must return calls with the following contract signatures (the chain supplies `ctx`):
278
+
279
+ | Endpoint | Move call | Result |
280
+ |---|---|---|
281
+ | `claim-daily` | `game::claim_daily<C>(exchange, account, cap, config, clock)` | None; XP only |
282
+ | `claim-quest` | `game::claim_quest<C>(exchange, account, cap, config, id: u8, clock)` | None; XP/cosmetics |
283
+ | `open-crate` | `game::open_crate<C>(exchange, account, cap, config, random, clock, ctx)` | None; private entry called directly as a PTB MoveCall |
284
+ | `open-crates` (not available until unmerged #184 is merged and published) | `game::open_crates<C>(exchange, account, cap, config, count: u64, random, clock, ctx)` | None; private entry called directly as a PTB MoveCall, one `CrateOpenedEventV1` per crate |
285
+ | `collect-mint` | `game::collect_mint<C, R>(exchange, account, cap, config, clock, ctx)` | `Coin<R>` to transfer |
286
+
287
+ **Pending #184.** `game::open_crates`, `EBadBatch` and the deferred batch accounting below are the unmerged batch-open contract; against the current contracts `open-crates` fails. `getOpenCratesTx` takes `count`, the crates to open in one transaction: a u64 of at least 1, as a safe integer, a bigint or a digit string, sent as a js_bigint (`"3n"`). A missing or empty `count` is a local `count_required`, and 0, a value above u64 or a malformed one a local `bad_count`, before any request. There is no app-level batch cap and no crate cooldown. On chain, `count` 0 aborts `game::EBadBatch` (13; `abortMessage("game", 13)` has copy in `GAME_ABORT_MESSAGES`), more crates than the player holds aborts `ENoCrate` (7), and a batch past Sui's per-transaction limits (1,024 events: one `CrateOpenedEventV1` per crate plus crate-count achievement / quest events; the computation cap) fails whole, so no crate of it opens. Keep batches to a few hundred (the app uses at most 500). Crate XP is credited at once, but a batch's tier-ups and `xp_per_crate` crate grants are deferred: they land (`TierUpEventV1` / `CrateGrantedEventV1`) at the player's next `open_crates`, before its crate check (so crates they grant can be opened in that call), or next XP grant (trade close, daily, quest, achievement). The player's openable crates are the unopened ones plus the pending grants, `floor(xp / r) - floor(effects_xp / r)` at the batch's rate `r = Player.effects_rate` (a later `xp_per_crate` change does not move it).
288
+
289
+ Daily, quest and crate calls have no reward type argument `R` or result to transfer. `DailyClaimedEventV1.amount` is the drop in units, granted as 10 XP each (`XpGrantedEventV1` carries the XP); `CrateOpenedEventV1.kind` is 1 (XP) or 2 (cosmetic), with money kind 0 removed. `getGame` already returns the wallet's `pendingMint` (on-chain u128), `feeCredit` and `feeCreditUsed` as `bigint`s; on the wire they are js_bigint literals like every other response integer (`"123n"`), and a plain decimal string there is `bad_body`. Game administration other than `getGameAttachTx` (definitions, registries, thresholds) has no builder here, and there is no BCS decoder for game events; read those from the API. Trade abort codes 31–32 are in `TRADE_ABORT_MESSAGES` (30 is reserved, with no copy).
290
+
291
+ ### Price receipts
292
+
293
+ There is no TWAP deviation guard. `oracle::begin_price(config, market, quote, clock)` (the storage price, equal to the signed price) serves emergency close, `close-retired`, `liquidate`, `liquidate-retired`, `sweep` and `check-market`. Two-step open and close fills (including liquidate-on-close) take `oracle::begin_fill_price` instead: the signed Lazer price verified inside the fill (verify-in-fill). It checks the feed pin, a positive price, and the market's `max_age_ms` / `max_future_ms` (10s / 6s ceilings) on the signed timestamp. Confidence is optional; there is no bid/ask and no signed band. Two-step fills settle at the signed price's plain mid with no spread; every other path settles on the storage mid. One-step paths (emergency close, `close-retired`) charge `max(confidence or 0, price * one_step_spread_bps)`, clamped to `price - 1`; they never abort `oracle::EBadSpread` (6), which is reserved and never raised. Outcome 12 is reserved and never emitted. A keeps the older liquidation-specific entry only as an interface-compatibility alias of `begin_price`; the service builds `begin_price` for every storage-priced PTB, liquidations included (keeper fills use `begin_fill_price`). No open, close, emergency-close, keeper-fill or liquidation path aborts with `oracle::ETwapDeviation` (3), `trade::ETwapUnchecked` (30) or `order::ETwapUnchecked` (18). The Move constants are deleted and each number is left as a reserved comment, never raised, so no other abort code shifts (`ORACLE_RESERVED_ABORT_CODES`, `TRADE_RESERVED_ABORT_CODES`, `ORDER_RESERVED_ABORT_CODES`). The SDK gives them no name and no copy (`orderAbortName` / `orderAbortMessage` / `tradeAbortMessage` return `undefined`), so they fall through to the caller's generic unknown-abort message, never to price-movement copy. `OracleParamsV1.max_twap_dev_bps` stays in the on-chain layout (frozen `create_market` / `set_oracle_params` signatures) but is no longer validated or enforced.
294
+
295
+ ### Abort codes added by the pre-publish review
296
+
297
+ The SDK exports `TRADE_ABORT_MESSAGES` / `tradeAbortMessage(code)` for trade abort copy. The API's catalog (`limits.moveAbortCatalog`) must still include every module:
298
+
299
+ | Module | Constant | Code | Meaning |
300
+ |---|---|---|---|
301
+ | `trade` | (none) | 30 | Reserved; never raised (removed price guard). No SDK name or copy |
302
+ | `trade` | `EEntryPriceWorse` | 31 | Not raised by two-step fills or emergency close (acceptable prices are not enforced; outcome 4 is unused). One-step paths with an explicit bound only ("Price moved beyond your slippage limit") |
303
+ | `trade` | `EExitPriceWorse` | 32 | Not raised by two-step fills or emergency close (emergency close passes no bound). One-step paths with an explicit bound only ("Price moved beyond your slippage limit") |
304
+ | `timelock` | `EWrongCap` | 7 | `commit_upgrade` got a receipt for a different slot's `UpgradeCap` |
305
+ | `account` (from `fee_mint::mint_on_loss`) | `EWrongExchange` | 5 | `mint_on_loss` with a ticket/account pair from another exchange |
306
+
307
+ `fee_mint::mint_on_loss` (built by `getClaimPendingMintTx`) mints for the account owner into the exchange's 7-day lock. After the lock, rewards vest linearly over 30 days; `locked_mint::release_unlocked` pays the vested portion. Its returned coin is always zero, so a keeper or delegate can submit it without receiving the PARCHMENT.
308
+
309
+ ### Staking (`parchment_token::staking`)
310
+
311
+ `stake(pool, config, coin, clock, ctx)`, `unstake(pool, config, amount, clock, ctx)`, `claim(pool, config, clock, ctx)` and `deposit_reward(pool, fee, clock)` all take the `0x6` clock; the first three also take the protocol `Config` (pause gates). Staker rewards stream: each deposit vests linearly over `reward_stream_ms` (default 7 days), so a stake entered in the same transaction as a deposit earns nothing. Each stake increase restarts a `min_stake_lock_ms` lock (default 0: no unstake wait at launch, admin-adjustable); claims are never locked.
312
+
313
+ | Reader | Route | Fields |
314
+ | --- | --- | --- |
315
+ | `getStakingPool()` | `GET /staking` | `poolId`, `configId`, `totalStaked`, `accRewardPerShare`, `streamRemaining`, `streamLastMs`, `streamEndMs`, `streamVestedNow`, `rewardStreamMs`, `minStakeLockMs`, `rewards`, `referralReserve`, `nowMs` (`null` until indexed) |
316
+ | `getStaker({ walletAddress })` | `GET /staking/{owner}` | `staked`, `rewardDebt`, `stakedAtMs`, `unlockAtMs`, `locked`, `pendingReward` (replays `pending_now`), `nowMs` |
317
+
318
+ | Code | Name | When |
319
+ | --- | --- | --- |
320
+ | 9 | `EBadStreamDuration` | `set_reward_stream_ms` outside 1 hour to 30 days |
321
+ | 10 | `EBadStakeLock` | `set_min_stake_lock_ms` above 30 days |
322
+ | 11 | `EStakeLocked` | `unstake` before `unlockAtMs` |
323
+ | 12 | `EWrongConfig` | an admin setter with a config that does not own the pool |
324
+
325
+ Admin setters `set_reward_stream_ms` / `set_min_stake_lock_ms(pool, config, AuthorityCap<PACKAGE, ADMIN>, ms)` (admin actions 77 / 78) emit `StakingAdminActionEventV1 { config_id, pool_id, action }` plus `RewardStreamUpdatedEventV1` / `MinStakeLockUpdatedEventV1`. The SDK does not build them.
326
+
327
+ ### Upgrade timelock (admin, not built by this SDK)
328
+
329
+ The SDK has no timelock builders or readers; upgrades are an admin/ops flow (`contracts/README.md` in the contracts repo). One shared `Timelock` can hold one slot per package, keyed by the package's original ID; at launch it is empty, because the four `UpgradeCap`s stay with the publisher and depositing is optional and one-way. For a deposited cap, every call takes that `package_id`: `schedule_upgrade(lock, config, cap, package_id, digest, clock)`, `cancel_upgrade(lock, config, cap, package_id)`, `execute_upgrade(lock, config, cap, package_id, digest, clock)`, `commit_upgrade(lock, package_id, receipt)`; views `has_upgrade_cap`, `upgrade_version`, `upgrade_package`, `pending_upgrade` (digest option, eta ms) and `min_delay_ms`. There is no upgrade delay (2026-10-20): `eta` is the schedule time, `min_delay_ms` always returns 0, and schedule + execute + commit can run in one PTB. `raise_delay` and the aborts `EDelayTooShort` (1), `ENotReady` (3) and `EDelayTooLong` (8) were removed.
330
+
331
+ ## Limits
332
+
333
+ - One-step settlement prices come from the market's pinned `PriceFeedStorage`, and only when the signed Lazer price equals that storage price (adapter 9 / 10): emergency close, retired close, liquidation and sweep transactions push the signed update (`price_feed_storage::update_price_feed` on `config.lazerIntegrationPackageId`), then call `adapter::quote` (storage id, source id, Lazer feed id) and `begin_price`. Request-open and request-close bytes carry no price; the keeper fills them with `adapter::quote_signed` then `begin_fill_price`. A feed marked `price_unavailable` stays that way; the SDK does not fill in a settlement price.
334
+ - `getEmergencyCloseTx` builds bytes that push the update, then call on-chain `quote` and `begin_price`. A liquidation PTB is `price_feed_storage::update_price_feed` → `parchment_oracle::adapter::quote(state, storage, update, expected_storage_id, source_id, expected_feed_id, clock)` → `oracle::begin_price<C>(config, market, quote, clock)` → `trade::liquidate` / `trade::liquidate_retired` / `trade::sweep`, which is what `getLiquidateTx` and `getSweepTx` build; those three calls are order-keeper only (trade 36), so the bytes only succeed when a keeper signs them. The public path is `trade::flag_liquidatable` then `trade::liquidate_after_grace` after `liq_grace_ms`. The pin ids (`expected_storage_id`, `source_id`, `expected_feed_id`) are passed as values read off chain from the market's oracle pins; in `quote`, a feed id missing from the update or a storage id that does not match aborts `adapter::EBadFeed` (2) and a source id above `u16::MAX` aborts `adapter::EBadSource` (5); a quote that passes but does not match the market's pin aborts `oracle::EBadFeed` (5) in `begin_price`. The API `note` is returned unchanged.
335
+ - There is no order book or bid/ask: two-step fills settle at the plain mid. The one-step spread is `price.spreadWad`: `max(confidence or 0, price * one_step_spread_bps)`; a signed confidence can only widen it. Previews return `spreadSource: "floor"`, or `"market-latest"` when a fresh signed confidence is indexed (which also sets `confWad` and `indicative: false`).
336
+ - `estimatedDirectBonus` and `estimatedSecondBonus` are off-chain estimates (10% and 1% of referee claim events) of the total accrued to date, paid or still owed; they are not the outstanding `staking::referral_owed` balance. Since J5 the chain records each bonus as owed (`staking::referral_owed`, `ReferralBonusAccruedEventV1`) and pays it from the treasury through `staking::claim_referral_bonus`; a short `referral_reserve` leaves the unpaid part owed. This SDK has no builder for `claim_referral_bonus`. `estimate` is the sentence the service returns with those figures.
337
+ - `getHarvestTx` credits the exchange-side free balance, not a wallet coin. `maxEntries` defaults to 32. A value outside 1 to 128 is rejected with `bad_amount` (B's code) before the request. The call takes no account objects.
338
+ - Transaction builders never attach a sponsor (`sponsor` is not sent); sponsorship is the separate `sponsorTransaction` step (see Gas sponsorship). Merging an incoming transaction kind is not available.
package/SECURITY.md ADDED
@@ -0,0 +1,25 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please do not open a public issue for a security problem. Email it to
6
+ [parchment-trade@protonmail.com](mailto:parchment-trade@protonmail.com) with:
7
+
8
+ - what is affected (SDK version, method, and the contract or API route if relevant),
9
+ - steps to reproduce, or a proof of concept,
10
+ - the impact you expect, such as funds at risk or a transaction that does not match its request.
11
+
12
+ If you cannot use email, you can also send a DM to [@tradeparchment on X](https://x.com/tradeparchment).
13
+
14
+ We aim to acknowledge reports within two business days and will keep you updated until the issue is fixed.
15
+ Please give us reasonable time to release a fix before you disclose anything publicly.
16
+
17
+ ## Scope
18
+
19
+ This policy covers this SDK: the API client, the transaction builders, the command-shape checks, and the optional `txTrust`
20
+ intent checks that inspect a built transaction before it is signed. Report problems with the Parchment smart
21
+ contracts or the hosted API the same way.
22
+
23
+ ## Supported versions
24
+
25
+ Security fixes go into the latest published minor version. Upgrade to the latest release before you report.
@@ -0,0 +1,2 @@
1
+ export { Parchment } from "./parchment.js";
2
+ export { ParchmentSdk } from "./parchmentSdk.js";
@@ -0,0 +1,2 @@
1
+ export { Parchment } from "./parchment.js";
2
+ export { ParchmentSdk } from "./parchmentSdk.js";
@@ -0,0 +1 @@
1
+ export { Parchment } from "../../packages/parchment/parchment.js";
@@ -0,0 +1 @@
1
+ export { Parchment } from "../../packages/parchment/parchment.js";
@@ -0,0 +1,23 @@
1
+ import { Parchment } from "../../packages/parchment/parchment.js";
2
+ import type { ParchmentOptions } from "../types/configTypes.js";
3
+ /**
4
+ * Entry point for the Parchment API.
5
+ *
6
+ * Create it with {@link ParchmentSdk.create}, then open the package with
7
+ * {@link ParchmentSdk.Parchment}. Each call returns a new package client.
8
+ */
9
+ export declare class ParchmentSdk {
10
+ private readonly options;
11
+ /**
12
+ * Creates a client. This does not contact the network.
13
+ *
14
+ * @param options - API origin. `baseUrl` is the origin only.
15
+ */
16
+ static create(options: ParchmentOptions): Promise<ParchmentSdk>;
17
+ constructor(options: ParchmentOptions);
18
+ /**
19
+ * Creates the Parchment package client.
20
+ * @returns A new {@link Parchment} client for this origin.
21
+ */
22
+ Parchment: () => Parchment;
23
+ }
@@ -0,0 +1,11 @@
1
+ import { Parchment } from "../../packages/parchment/parchment.js";
2
+ export class ParchmentSdk {
3
+ options;
4
+ static async create(options) {
5
+ return new ParchmentSdk(options);
6
+ }
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ Parchment = () => new Parchment(this.options);
11
+ }
@@ -0,0 +1,62 @@
1
+ import type { ParchmentTxTrustSetting } from "../../packages/parchment/txIntent.js";
2
+ /**
3
+ * Settings for HTTP calls.
4
+ *
5
+ * `baseUrl` is the origin of the Parchment API. `apiEndpoint` is the path
6
+ * segment before `parchment` and defaults to `api`, so calls land on
7
+ * `{baseUrl}/api/parchment/...`.
8
+ */
9
+ export interface CallerConfig {
10
+ baseUrl?: string;
11
+ apiEndpoint?: string;
12
+ /** Optional fetch implementation. Defaults to the global fetch. */
13
+ fetch?: typeof fetch;
14
+ /** Opt-in per-request timeout, see {@link RequestOptions}. Default: no timeout. */
15
+ timeoutMs?: number;
16
+ /** Opt-in cancellation signal applied to every request, see {@link RequestOptions}. */
17
+ signal?: AbortSignal;
18
+ /** Checks of builder responses, see {@link ParchmentOptions.txTrust}. */
19
+ txTrust?: ParchmentTxTrustSetting;
20
+ }
21
+ /**
22
+ * Opt-in request limits. Neither is set by default, so a request waits as long as `fetch` does.
23
+ *
24
+ * `timeoutMs` applies to each HTTP request separately (headers and body). When it passes, the
25
+ * request is aborted and rejects with `ParchmentApiError` code `"timeout"` (status 0). When
26
+ * `signal` aborts, the request rejects with the signal's reason, like `fetch`. A timed-out
27
+ * transaction-builder POST built nothing on chain; only a signed and executed transaction moves funds.
28
+ */
29
+ export interface RequestOptions {
30
+ timeoutMs?: number;
31
+ signal?: AbortSignal;
32
+ }
33
+ /**
34
+ * Argument of `Parchment.withRequestOptions`: a field left out or `undefined` keeps the inherited
35
+ * value, `null` clears it, any other value replaces it.
36
+ */
37
+ export type RequestOptionsUpdate = {
38
+ [K in keyof RequestOptions]?: RequestOptions[K] | null;
39
+ };
40
+ /** Options for {@link Parchment.create}. `baseUrl` is required. */
41
+ export interface ParchmentOptions {
42
+ /** Origin of the Parchment API, with no path. */
43
+ baseUrl: string;
44
+ /** Path segment before `parchment`. Defaults to `api`. */
45
+ apiEndpoint?: string;
46
+ fetch?: typeof fetch;
47
+ /** Opt-in per-request timeout in ms for every call of this client. Default: none. */
48
+ timeoutMs?: number;
49
+ /** Opt-in signal that cancels every call of this client. */
50
+ signal?: AbortSignal;
51
+ /**
52
+ * Builder trust. Default (omitted, or `"unchecked"` / `UNCHECKED_TX`): the API is trusted for the
53
+ * packages and arguments it builds; the SDK checks only the command shapes (no gas-coin split, no
54
+ * transfer to anyone but the sender, no publish or upgrade). Opt-in strict mode: package ids
55
+ * pinned from your own build, and the service's trigger delegate. Every builder response, from
56
+ * this client and the account and market clients it returns, must then call only those packages
57
+ * and exactly the request's action with its objects, amounts, delegates and signed update
58
+ * (`txIntentViolation`), else `ParchmentApiError` `intent_mismatch`; a `getConfig()` naming other
59
+ * package ids is `untrusted_package`. Strict pins need updating with each package upgrade.
60
+ */
61
+ txTrust?: ParchmentTxTrustSetting;
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type { CallerConfig, ParchmentOptions, RequestOptions, RequestOptionsUpdate } from "./configTypes.js";