@trinaryex/sdk 3.9.0 → 5.0.0

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 (47) hide show
  1. package/DESIGN.md +253 -71
  2. package/README.md +220 -16
  3. package/dist/ReadOnlyClient.d.ts +42 -8
  4. package/dist/TriexClient.d.ts +73 -8
  5. package/dist/armature/OrgClient.d.ts +372 -0
  6. package/dist/armature/OrgClientGroups.d.ts +186 -0
  7. package/dist/armature/OrgsApi.d.ts +69 -0
  8. package/dist/armature/actions.d.ts +75 -0
  9. package/dist/armature/create.d.ts +27 -0
  10. package/dist/armature/currency.d.ts +64 -0
  11. package/dist/armature/executors.d.ts +44 -0
  12. package/dist/armature/governance.d.ts +194 -0
  13. package/dist/armature/harness.d.ts +96 -0
  14. package/dist/armature/lifecycle.d.ts +84 -0
  15. package/dist/armature/plan.d.ts +39 -0
  16. package/dist/armature/proposals.d.ts +51 -0
  17. package/dist/armature/queries.d.ts +16 -0
  18. package/dist/armature/schemas.d.ts +559 -0
  19. package/dist/armature/trading.d.ts +114 -0
  20. package/dist/armature/transactions.d.ts +160 -0
  21. package/dist/armature/treasury.d.ts +41 -0
  22. package/dist/armature/tree.d.ts +13 -0
  23. package/dist/armature/types.d.ts +59 -0
  24. package/dist/armature/upgrade.d.ts +27 -0
  25. package/dist/armature/vault.d.ts +94 -0
  26. package/dist/coins/CoinsApi.d.ts +53 -0
  27. package/dist/coins/funding.d.ts +6 -0
  28. package/dist/coins/money.d.ts +36 -0
  29. package/dist/coins/onchain.d.ts +162 -0
  30. package/dist/coins/schemas.d.ts +456 -0
  31. package/dist/coins/transactions.d.ts +82 -0
  32. package/dist/coins/types.d.ts +115 -0
  33. package/dist/errors.d.ts +11 -1
  34. package/dist/funding.d.ts +2 -2
  35. package/dist/http.d.ts +3 -0
  36. package/dist/index.d.ts +63 -8
  37. package/dist/index.js +10620 -959
  38. package/dist/money.d.ts +15 -0
  39. package/dist/moveAbort.d.ts +28 -0
  40. package/dist/moveAbortCatalog.generated.d.ts +9 -0
  41. package/dist/onchain.d.ts +55 -3
  42. package/dist/paging.d.ts +9 -3
  43. package/dist/queries.d.ts +35 -7
  44. package/dist/schemas.d.ts +2257 -50
  45. package/dist/transactions.d.ts +63 -1
  46. package/dist/types.d.ts +118 -7
  47. package/package.json +3 -1
package/DESIGN.md CHANGED
@@ -11,8 +11,8 @@ first module, with room to grow into a higher-level, full-featured client.
11
11
  **Audience:** players and bots trading on Trinary Exchange via an API key.
12
12
  **Target (MVP):** **testnet only**, the **`stillness`** tenant; **only the most-recent (v1)
13
13
  [CLOB contracts](https://github.com/loash-industries/trinary-exchange)** are supported.
14
- Item↔currency (CRED) markets via `multicoin_pool` ("pools"); coin-pools are out of scope
15
- for now.
14
+ Item↔currency (CRED) markets via `multicoin_pool` ("pools"); coin (currency-pair) pools
15
+ were added for cycle 7 as the `coins` group — see [DESIGN-COINS.md](./DESIGN-COINS.md).
16
16
  **Models after:** [`@trinaryex/keyspace`](https://www.npmjs.com/package/@trinaryex/keyspace)
17
17
  (client + `executor` pattern, `queries`/`transactions` split, zod validation, vite build,
18
18
  semantic-release).
@@ -61,20 +61,20 @@ The "Gateway" column is the **current** state in
61
61
 
62
62
  | # | User story | Plane | Indexer endpoint / Move entrypoint | Gateway today |
63
63
  |---|---|---|---|---|
64
- | 1 | Create trading account **iff** none exists (balance manager) | READ+WRITE | READ on-chain `listOwnedObjects` (authoritative; `GET /v1/inventory/balance-manager` stays disabled) → WRITE `balance_manager::new()` (+ `transferObjects` to self) | n/a (on-chain read) |
64
+ | 1 | Create trading account **iff** none exists (balance manager) | READ+WRITE | READ on-chain `listOwnedObjects` (authoritative; `GET /v1/inventory/balance-manager` stays disabled) → WRITE `trading_account::new()` (+ `transferObjects` to self) | n/a (on-chain read) |
65
65
  | 2 | Fetch item balances | READ | `GET /v1/inventory/balances` — **hub-scoped** (`storage_unit_id` required): warehouse (wallet receipts, by `owner_address`), marketplace (BM, by `balance_manager_id`), hangar (by `inventory_key` owner-cap id) | **enabled** (50 CU) |
66
66
  | 3 | Fetch trade-currency (CRED) balance | READ | **fullnode** — wallet coins (`listCoins`) + BM `BalanceKey<CRED>` dynamic field; the indexer inventory endpoint serves *items only* | n/a (fullnode) |
67
- | 4 | Deposit items from hangar → trading account | READ+WRITE | READ `GET /v1/hubs/{hub_id}/vault` (vaultConfigId + collectionId) + on-chain owner-cap/char resolution → WRITE the **direct-from-hangar sequence** (borrow_owner_cap → receipt::deposit_for_receipt → return_owner_cap → balance_manager::deposit_multicoin); see §6.1 | **enabled** (vault) |
68
- | 5 | Deposit currency → trading account | WRITE | `balance_manager::deposit<CRED>` (wallet coin selected/merged/split) | n/a (on-chain) |
67
+ | 4 | Deposit items from hangar → trading account | READ+WRITE | READ `GET /v1/hubs/{hub_id}/vault` (vaultConfigId + collectionId) + on-chain owner-cap/char resolution → WRITE the **direct-from-hangar sequence** (borrow_owner_cap → receipt::deposit_for_receipt → return_owner_cap → trading_account::deposit_multicoin); see §6.1 | **enabled** (vault) |
68
+ | 5 | Deposit currency → trading account | WRITE | `trading_account::deposit<CRED>` (wallet coin selected/merged/split) | n/a (on-chain) |
69
69
  | 6 | Discover items with live buy/sell orders across the universe | READ | `GET /v1/discovery` — **open orders sorted by recency**, with filters (hubs/item/side/bm/public) | **enabled** (150 CU) |
70
70
  | 7 | Fetch trade-hub details (public/private, owner, location) | READ | `GET /v1/hubs/{hub_id}/vault` + `GET /v1/hubs/{hub_id}/location` (+ `GET /v1/collections/{collection_id}/hub` reverse lookup). `/location` **404s semantically** for unrevealed hubs (most of them) → `TradeHubDetail.location` is nullable. Tribe/fuel are NOT in these responses (post-MVP: hubs economics/enriched routes) | **enabled** |
71
71
  | 8 | Fetch items with buy/sell orders at a specific storage unit | READ | `GET /v1/hubs/{hub_id}/items` (`has_bids`/`has_asks` flags) | **enabled** |
72
72
  | 9 | Fetch order book for one item at that storage unit | READ | `GET /v1/hubs/{hub_id}/vault` → `GET /v1/pools/resolve` (**`collection_id`+`asset_id`**, not hub+item) → `GET /v1/pools/{pool_id}/orderbook` (returns resting **orders**, not levels) | **enabled** |
73
73
  | 10 | Create **limit** buy/sell order | READ+WRITE | READ resolve chain + `GET /v1/pools/{pool_id}/metadata` (fee is 1e9-scaled `fee`, not bps) → WRITE `multicoin_pool::place_limit_order<Quote>` + deposit deficit | **enabled** (reads) |
74
74
  | 11 | Create **market** buy/sell order | READ+WRITE | as #10 but `multicoin_pool::place_market_order<Quote>` (no price/expiry) | **enabled** (reads) |
75
- | 14 | Read own open orders / fills / trades (bots) | READ | `GET /v1/balance-managers/{bm}/open-orders`, `/fills`, `/trades` (epoch-ms `before`/`after` paging) | **enabled** |
76
- | 12 | Withdraw items from BM → storage unit | WRITE | `balance_manager::withdraw_all_multicoin` → `receipt::redeem_receipt(...ssu, character...)` | n/a (on-chain) |
77
- | 13 | Withdraw currency from BM → wallet | WRITE | `balance_manager::withdraw_all<CRED>` + `transferObjects` to self | n/a (on-chain) |
75
+ | 14 | Read own open orders / fills / trades (bots) | READ | `GET /v1/trading-accounts/{trading_account_id}/open-orders`, `/fills`, `/trades` (epoch-ms `before`/`after` paging; `next_cursor` is the last row's timestamp, null on a short page) | **enabled** |
76
+ | 12 | Withdraw items from BM → storage unit | WRITE | `trading_account::withdraw_all_multicoin` → `receipt::redeem_receipt(...ssu, character...)` | n/a (on-chain) |
77
+ | 13 | Withdraw currency from BM → wallet | WRITE | `trading_account::withdraw_all<CRED>` + `transferObjects` to self | n/a (on-chain) |
78
78
 
79
79
  **Phase 0 status (2026-08-21): done.** The market surface (pools, hubs, collections,
80
80
  balance-manager reads, discovery) was enabled in DCR `62acb22`;
@@ -93,6 +93,49 @@ on the free/standard tier).
93
93
  > (no lag) and are not "reads" in the indexer sense. The BM-existence check (#1) is likewise
94
94
  > best done on-chain (`listOwnedObjects`) to avoid the double-create race (§13).
95
95
 
96
+ ### 2.1 Beyond the stories — full gateway read coverage (2026-10-01)
97
+
98
+ Every operation the gateway publishes now has an SDK method (bar `/v1/coins`,
99
+ owned by the coin-pool module, and the deprecated `/v1/tribes/{id}` alias).
100
+ `test/gateway.test.ts` enforces it: a newly published, non-deprecated route
101
+ fails the suite until it is wrapped or explicitly excluded, and
102
+ `test/schema-conformance.test.ts` parses payloads synthesised from every
103
+ wrapped operation's response schema (minimal: required fields only, nullables
104
+ null; full: every field) so a requiredness/nullability drift fails CI before it
105
+ throws on live data.
106
+
107
+ | Need | Endpoint | SDK | CU |
108
+ |---|---|---|---|
109
+ | What became of an order (open / filled / cancelled + fills) | `GET /v1/pools/{pool_id}/orders/{order_id}` | `orders.get` · `ro.order` | 30 |
110
+ | One fill, both sides + both fees | `GET /v1/fills/{event_digest}` | `orders.fill` · `ro.fill` | 30 |
111
+ | Public tape, all item markets | `GET /v1/trades/recent` | `market.recentTrades` · `iterateRecentTrades` | 50 |
112
+ | Plain market price (no fee), item-wide or per hub | `GET /v1/display-prices`, `/{item_id}` | `market.displayPrices` / `displayPrice` | 50 / 20 |
113
+ | Hub fee reserve + depth | `GET /v1/hubs/economics` | `market.hubEconomics` | 50 |
114
+ | Pools by unclaimed fees | `GET /v1/pools/top-by-fees` | `market.topPoolsByFees` | 50 |
115
+ | Platform aggregates | `GET /v1/stats` | `market.stats` | 50 |
116
+ | Characters | `GET /v1/characters/{id}`, `/address/{a}`, `/name/{n}`, `/batch` | `characters.get` / `byAddress` / `byName` / `batch` | 20 / 20 / 20 / 50 |
117
+ | Tribes | `GET /v1/world/tribes/{tribe_id}` | `characters.tribe` | 20 |
118
+ | World items & recipes (CDN, static) | `GET /v1/world/items`, `/{asset_id}`, `/v1/world/recipes`, `/{item_id}` | `world.items` / `item` / `recipes` / `recipesFor` | 20 each |
119
+ | Routing (location-api) | `GET /v1/routing/route`, `/compare`, `/stats` | `routing.route` / `compare` / `stats` | 100 / 300 / 20 |
120
+
121
+ Notes that change how results are read:
122
+
123
+ - **Units on the public tape differ.** `/v1/trades/recent` returns
124
+ HUMAN-READABLE `price` / `quantity` / `fee_amount` (already shifted by the
125
+ quote decimals) — the SDK keeps them as strings. Every other trade/fill read
126
+ is raw integers → `bigint`.
127
+ - **Fees (cycle 7).** Pool metadata's `fee` is the fee class's ENTRY tier (the
128
+ higher of old/new while a class change is pending) — an upper bound, right
129
+ for deposit buffers. The fee a fill charged is on the fill; `fee_rate_bps`
130
+ on the tape is derived per fill (`taker_fee × 10000 / quote_quantity`), and
131
+ the indexer now records maker fees too. Display prices carry no fee.
132
+ - **Solar system names are player-reported (cycle 7).** location-api fills
133
+ names from EF-Map community reports; until a system is named, its
134
+ `solar_system_name` is null and name lookups 404 (`SolarSystemNotFound` /
135
+ `RouteNotFound`) while id lookups work. Routing is name-only.
136
+ `/v1/spatial/stats` gains `known_solar_system_names`. The SDK accepts both
137
+ the published (string) and post-deploy (nullable) shapes.
138
+
96
139
  ---
97
140
 
98
141
  ## 3. Architecture
@@ -104,8 +147,11 @@ on the free/standard tier).
104
147
  │ TriexClient (high-level facade) │
105
148
  │ ├── account (balance manager lifecycle) │
106
149
  │ ├── balances (item + currency reads) │
107
- │ ├── market (discovery, hubs, orderbook) │
108
- │ └── orders (limit/market, deposit/wdrw) │
150
+ │ ├── market (discovery, hubs, orderbook, │
151
+ │ │ tape, prices, stats) │
152
+ │ ├── orders (limit/market, status reads) │
153
+ │ ├── spatial · routing (star map, routes) │
154
+ │ └── characters · world (players, items) │
109
155
  │ │
110
156
  │ queries.ts ──READ──► api.trinary.exchange│───► etl-api (indexer)
111
157
  │ (fetch + x-api-key + zod validate) │ (Postgres read models)
@@ -228,9 +274,9 @@ observe. Currency balances (a fullnode read) live on `TriexClient.balances.curre
228
274
  ### 5.2 Methods → user stories
229
275
 
230
276
  ```ts
231
- // account (balance manager lifecycle)
277
+ // account (trading account lifecycle)
232
278
  client.account.get(address): Promise<TradingAccount | null> // #1 read (on-chain)
233
- client.account.ensure(): Promise<{ balanceManagerId: string; created: boolean }> // #1 write (idempotent)
279
+ client.account.ensure(): Promise<{ tradingAccountId: string; created: boolean }> // #1 write (idempotent)
234
280
 
235
281
  // balances — items are hub-scoped indexer reads; currency is a fullnode read
236
282
  client.balances.atHub({ storageUnitId, inventoryKey? }): Promise<InventoryBalances> // #2 (warehouse/marketplace/hangar)
@@ -259,7 +305,7 @@ client.market.hubsEnriched({ hubIds }): Promise<HubEnriched[]> // batch
259
305
  client.market.assemblyOwners({ assemblyIds }): Promise<AssemblyOwner[]> // batch ≤200
260
306
  client.market.assembliesEnriched({ assemblyIds }): Promise<AssemblyEnriched[]> // batch ≤200: + character/name
261
307
  client.market.solarSystemNames({ solarSystemIds }): Promise<SolarSystemName[]> // batch ≤200
262
- client.account.owners({ balanceManagerIds }): Promise<BalanceManagerOwner[]> // batch ≤200: player:/ou: tagged
308
+ client.account.owners({ tradingAccountIds }): Promise<TradingAccountOwner[]> // batch ≤200: player:/ou: tagged
263
309
 
264
310
  // spatial — the star map (Universe | Solar Systems); no account or signer
265
311
  client.spatial.system(nameOrId): Promise<SolarSystem> // coords + constellation + region
@@ -273,15 +319,52 @@ client.spatial.stats(): Promise<SpatialStats> // star-ma
273
319
  client.orders.openOrders(params?): Promise<OpenOrdersPage>
274
320
  client.orders.fills(params?): Promise<FillsPage>
275
321
  client.orders.trades(params?): Promise<TradesPage>
322
+ client.orders.get({ poolId, orderId }): Promise<OrderDetail> // any order, any state, + fills
323
+ client.orders.fill(eventDigest): Promise<FillDetail> // both sides + both fees
324
+
325
+ // market-wide feeds, prices & rankings (§2.1)
326
+ client.market.recentTrades({ before?, after?, limit?, publicOnly?, assetId? }): Promise<RecentTradesPage>
327
+ client.market.displayPrices({ itemIds, storageUnitIds?, fallback? }): Promise<DisplayPrice[]>
328
+ client.market.displayPrice(itemId, { storageUnitId?, fallback? }?): Promise<DisplayPrice>
329
+ client.market.hubEconomics({ hubIds }): Promise<HubEconomics[]>
330
+ client.market.topPoolsByFees({ limit? }?): Promise<PoolFees[]>
331
+ client.market.stats(): Promise<PlatformStats>
332
+
333
+ // routing — names only; names are player-reported in cycle 7
334
+ client.routing.route({ origin, destination, optimization?, mass?, gateWeight?, maxJumpRangeLy? }): Promise<Route>
335
+ client.routing.compare({ origin, destination, mass?, gateWeight?, maxJumpRangeLy? }): Promise<RouteComparison>
336
+ client.routing.stats(): Promise<RoutingStats>
337
+
338
+ // characters & tribes
339
+ client.characters.get(characterId, { enrich? }?): Promise<Character>
340
+ client.characters.byAddress(address?, { enrich? }?): Promise<Character[]> // defaults to the player
341
+ client.characters.byName(name): Promise<Character[]>
342
+ client.characters.batch({ addresses }): Promise<CharacterLookup[]> // ≤500
343
+ client.characters.tribe(tribeId): Promise<Tribe>
344
+
345
+ // world reference data (static — cache it)
346
+ client.world.items(): Promise<WorldItem[]>
347
+ client.world.item(assetId): Promise<ItemInfo>
348
+ client.world.recipes(): Promise<Blueprint[]> // includes `reverse` — skip when costing
349
+ client.world.recipesFor(assetId): Promise<ItemBlueprints> // { producedBy, usedIn }; both [] when untouched
276
350
 
277
351
  // orders (#10, #11) — each auto-ensures BM + deposits any deficit in one PTB
278
352
  client.orders.limit({ storageUnitId, assetId, side, price, quantity, expireAt? }): Promise<TxResult> // #10
279
353
  client.orders.market({ storageUnitId, assetId, side, quantity, quoteBudget? }): Promise<TxResult> // #11
280
354
 
281
355
  // pulled forward from post-MVP (bots are not viable without them):
282
- client.orders.cancel({ storageUnitId, assetId, orderId }) / cancelAll / modify
356
+ client.orders.cancel({ storageUnitId, assetId, orderId }) / cancelMany({ …, orderIds }) / cancelAll / modify
357
+ client.orders.fees({ storageUnitId, assetId } | { poolId }): Promise<TradingFees> // ladder + own tier (fullnode)
283
358
  client.account.sweepable(): Promise<Sweepable> // claimable proceeds + idle BM items
284
359
  client.account.claimSettled({ poolIds? }) // withdraw_settled_amounts per pool
360
+
361
+ // trading-account administration (owner-only)
362
+ client.account.register() // registry::get_trading_account_ids bookkeeping
363
+ client.account.mintCap({ kind, recipient? }) / revokeCap({ capId }) / caps(address?)
364
+
365
+ // item-pool lifecycle
366
+ client.market.createPool({ storageUnitId, assetId }) // 500 CRED, permissionless
367
+ client.market.claimOperatorShare({ poolIds }) // hub revenue share, permissionless
285
368
  ```
286
369
 
287
370
  `side: 'buy' | 'sell'` maps to `isBid`; items are identified by `assetId` (the indexer's
@@ -294,23 +377,51 @@ are inferred from the pinned zod wire schemas (`schemas.ts`) — single source o
294
377
 
295
378
  ## 6. On-chain transaction builders (`transactions.ts`)
296
379
 
297
- Confirmed Move entrypoints (from `triex-app-api` — personal, non-governance path). All are
298
- pure functions `(args) => Transaction`; the facade fills object IDs from indexer reads.
380
+ Confirmed Move entrypoints (personal, non-governance path), verified against
381
+ trinary-exchange `main` (cycle 7 + TRIEX-158): the `balance_manager` module is now
382
+ `trading_account` (`BalanceManager` → `TradingAccount`), order ids are `u128`, and
383
+ placement / cancel / cancel-many / cancel-all / modify / swaps take the shared `FeePolicy`
384
+ (`triexFeePolicy`) right after the pool. All are pure functions that append to a caller's
385
+ `Transaction`; the facade fills object IDs from indexer + fullnode reads. Unit tests pin
386
+ each target's argument order (`test/writes.test.ts`, `test/transactions.test.ts`).
299
387
 
300
388
  | Builder | Move target | Notes |
301
389
  |---|---|---|
302
- | `newBalanceManager` | `${triex}::balance_manager::new()` | returns BM; `transferObjects([bm], self)` when freshly created |
303
- | `depositCoin` | `${triex}::balance_manager::deposit<T>(bm, coin)` | coin prepared via list/merge/split of wallet coins |
304
- | `depositMulticoinObject` | `${triex}::balance_manager::deposit_multicoin(bm, object)` | deposit an owned multicoin `Balance` object (wallet receipts) into BM |
305
- | `withdrawAllCoin` | `${triex}::balance_manager::withdraw_all<T>(bm)` → `transferObjects` | #13 |
306
- | `withdrawAllMulticoin` | `${triex}::balance_manager::withdraw_all_multicoin(bm, collectionId, assetId)` | #12 step 1 |
390
+ | `newTradingAccount` | `trading_account::new()` | `transferObjects([bm], self)` when freshly created |
391
+ | `newTradingAccountWithOwner` / `…WithOwnerAndCaps` | `trading_account::new_with_custom_owner(_and_caps)(owner)` | builder only; the `_and_caps` caps go to `owner` on-chain |
392
+ | `registerTradingAccount` | `trading_account::register_trading_account(bm, registry)` | `account.register()`; ≤100 per owner |
393
+ | `mintTradingAccountCap` | `trading_account::mint_{trade,deposit,withdraw}_cap(bm)` | `account.mintCap()`; ≤1000 live caps |
394
+ | `revokeTradingAccountCap` | `trading_account::revoke_trade_cap(bm, &ID)` | `account.revokeCap()`; revokes any of the three cap kinds |
395
+ | `generateProofAsOwner` / `generateProofAsTrader` | `trading_account::generate_proof_as_{owner(bm), trader(bm, tradeCap)}` | proof before placing/cancelling/claiming |
396
+ | `depositCoin` / `depositCoinWithCap` | `trading_account::deposit<T>(bm, coin)` / `deposit_with_cap<T>(bm, cap, coin)` | coin prepared via list/merge/split of wallet coins |
397
+ | `depositMulticoinObject` / `depositMulticoinWithCap` | `trading_account::deposit_multicoin(bm, balance)` / `…_with_cap(bm, cap, balance)` | wallet receipts / hangar receipts into the account |
398
+ | `withdrawCoin` / `withdrawAllCoin` / `withdrawCoinWithCap` | `trading_account::withdraw<T>(bm, amount)` / `withdraw_all<T>(bm)` / `withdraw_with_cap<T>(bm, cap, amount)` | #13 |
399
+ | `withdrawMulticoin` / `withdrawAllMulticoin` / `withdrawMulticoinWithCap` | `trading_account::withdraw_multicoin(bm, collectionId, assetId, amount)` / `withdraw_all_multicoin(bm, collectionId, assetId)` / `…_with_cap` | #12 step 1 (`withdrawItems` uses the partial form when `amount` is set) |
307
400
  | `redeemReceipt` | `${warehouseReceipts}::receipt::redeem_receipt(balance, ssu, character, vaultConfig, collection, isOwner)` | #12 step 2 (BM item → hangar) |
308
- | `ownerProof` | `${triex}::balance_manager::generate_proof_as_owner(bm)` | required before placing/withdrawing |
309
- | `placeLimitOrderItem` | `${triex}::multicoin_pool::place_limit_order<Quote>(pool, bm, proof, orderType, selfMatch, price, qty, isBid, expireTs, clock)` | items (multicoin) |
310
- | `placeMarketOrderItem` | `${triex}::multicoin_pool::place_market_order<Quote>(pool, bm, proof, selfMatch, qty, isBid, clock)` | items (multicoin) |
311
- | `cancelOrderItem` | `${triex}::multicoin_pool::cancel_order<Quote>(pool, bm, proof, orderId u64, clock)` | implemented (+ `cancel_all_orders`, `modify_order`) |
312
- | `withdrawCoin` | `${triex}::balance_manager::withdraw<T>(bm, amount)` | partial currency withdraw |
313
- | `withdrawSettledAmounts` | `${triex}::multicoin_pool::withdraw_settled_amounts<Quote>(pool, bm, proof)` | claim post-fill proceeds into the BM |
401
+ | `placeLimitOrderItem` | `multicoin_pool::place_limit_order<Quote>(pool, policy, bm, proof, orderType, selfMatch, price, qty, isBid, expireTs, clock)` | `…_with_quote_fees` is an identical alias |
402
+ | `placeMarketOrderItem` | `multicoin_pool::place_market_order<Quote>(pool, policy, bm, proof, selfMatch, qty, isBid, clock)` | IOC at the max/min price |
403
+ | `cancelOrderItem` / `cancelOrdersItem` / `cancelAllOrdersItem` | `multicoin_pool::cancel_order(…, orderId u128, clock)` / `cancel_orders(…, vector<u128>, clock)` / `cancel_all_orders(…, clock)` | all `(pool, policy, bm, proof, …)` |
404
+ | `modifyOrderItem` | `multicoin_pool::modify_order<Quote>(pool, policy, bm, proof, orderId u128, newQty, clock)` | reduce only |
405
+ | `withdrawSettledAmounts` / `…Permissionless` | `multicoin_pool::withdraw_settled_amounts<Quote>(pool, bm, proof)` / `…_permissionless(pool, bm)` | no `FeePolicy` |
406
+ | `swapExact{Base,Quote}For{Quote,Base}Item` | `multicoin_pool::swap_exact_*<Quote>(pool, policy, in, credIn, minOut, clock)` | builder only: account-less swaps (temporary account on-chain) |
407
+ | `swapExact…WithTradingAccountItem` | `multicoin_pool::swap_exact_*_with_trading_account<Quote>(pool, policy, bm, tradeCap, depositCap, withdrawCap, in, minOut, clock)` | builder only |
408
+ | `createPermissionlessPoolItem` | `multicoin_pool::create_permissionless_pool<Quote>(registry, policy, collection, assetId, fee)` | `market.createPool()`; fee = 500 CRED |
409
+ | `claimOperatorShareItem` | `multicoin_pool::claim_operator_share<Quote>(pool, policy, registry, clock)` | `market.claimOperatorShare()` |
410
+ | `updatePoolAllowedVersionsItem` | `multicoin_pool::update_pool_allowed_versions<Quote>(pool, registry)` | builder only (post-upgrade maintenance) |
411
+
412
+ **Fee reads** (`onchain.ts` `getPoolTradingFees`, facade `orders.fees()`) are one
413
+ simulated transaction (`simulateTransaction` + `commandResults`, nothing executes) of the
414
+ view functions `multicoin_pool::{pool_fee_class, pool_fee_schedule,
415
+ pool_fee_schedule_next, trade_params_for_account, account_fee_tier,
416
+ account_fee_turnover}` and `fee_policy::cancel_retention_bps`.
417
+
418
+ **Deliberately not wrapped:** `trading_account::new_with_uid_owner_and_caps` (TRIEX-158 —
419
+ takes `&mut UID`, so only a Move module that owns the parent object can call it; Armature
420
+ uses it), every `TriexAdminCap`-gated function (`create_pool_admin`, `set_pool_fee_class`,
421
+ `unregister_pool_admin`, `update_allowed_versions`, `withdraw_pool_fees`, the
422
+ `fee_policy` class / operator-share / adapter setters, the `registry` setters), and
423
+ `fee_policy::register_operator_beneficiary_with_witness` (callable only through the
424
+ admin-registered adapter package's witness).
314
425
 
315
426
  > `<Quote>` is the CRED coin type (`credCoinType`). Item markets are `multicoin_pool`; the
316
427
  > item is identified by `collectionId` + `assetId` (u64), not a Move type parameter.
@@ -332,7 +443,7 @@ covering a deposit deficit from two sources in order:
332
443
 
333
444
  1. **Wallet multicoin receipts.** Find owned `${multicoin}::multicoin::Balance` objects for the
334
445
  `assetId` in the right collection, then for each:
335
- `balance_manager::deposit_multicoin(bm, object)`.
446
+ `trading_account::deposit_multicoin(bm, object)`.
336
447
  2. **SSU / character hangar inventory** (only if a deficit remains). For each relevant owner cap
337
448
  (SSU owner cap and/or character owner cap):
338
449
 
@@ -342,7 +453,7 @@ covering a deposit deficit from two sources in order:
342
453
  ssuObject, character, cap, vaultConfigId,
343
454
  vaultCollectionId, assetId /*u64*/, amount /*u32*/)
344
455
  character::return_owner_cap<CapType>(character, cap, borrowReceipt)
345
- balance_manager::deposit_multicoin(bm, receipt)
456
+ trading_account::deposit_multicoin(bm, receipt)
346
457
  ```
347
458
 
348
459
  **RQ-3 resolved (from the app):** the SSU owner-cap type arg is
@@ -372,17 +483,36 @@ back into the SSU/hangar (needs `ssu`, `character`, `vaultConfig`, `collection`,
372
483
  ## 7. Money math (`money.ts`)
373
484
 
374
485
  - **Coin (currency-pair) pools:** `quote = base * price / TRIEX_PRICE_SCALING`
375
- where `TRIEX_PRICE_SCALING = 1_000_000_000` (1e9).
486
+ where `TRIEX_PRICE_SCALING = 1_000_000_000` (1e9). Full cycle-7 model (maker + taker
487
+ fees, bid deposit bound, minimum size) in DESIGN-COINS.md §3; code in `src/coins/money.ts`.
376
488
  - **Multicoin (item) pools:** scaling factor `1` → `quote = price * quantity` (confirmed
377
489
  against the production app, which passes the unscaled price straight to
378
490
  `place_limit_order`; TRIEX_SYSTEM_DESIGN §7's blanket "all prices ×1e9" describes coin
379
491
  pools).
380
- - **Bid deposit overhead:** v1 pools charge a **quote-denominated fee**; a bid must deposit
381
- `quote × (1e9 + feeRateScaled) / 1e9` (floor-of-total — matches the app's
382
- `computeBidQuoteDeposit` and the on-chain per-fill floor). `feeRateScaled` is pool
383
- metadata's raw `fee` (scaled by 1e9; `20_000_000` = 2%, the default volatile fee) — the
384
- endpoint exposes **no bps field**. Only buyers pay fees; asks are fee-free. Market bids
385
- require an explicit `quoteBudget` (the app requires `quoteDepositAmount > 0`).
492
+ - **Fee model (cycle 7).** Every fee is quote-denominated and priced by
493
+ `quote_fee::fee_from_scaled_rate` = `floor(quote × min(rate, 1e9) / 1e9)` (`computeQuoteFee`).
494
+ Rates live in the shared `FeePolicy`: each pool carries a fee class whose ladder
495
+ (`fee_schedule::FeeSchedule`, ≤16 tiers, taker and maker rates non-increasing by tier;
496
+ multicoin launch ladder 2.2%/1.8% → 1.1%/0.9%) is resolved against the account's trailing
497
+ 30-epoch **fee turnover** (taker fees paid + maker fees earned at fill, exchange-wide per
498
+ quote). Schedule changes are staged for the next epoch. Who pays what
499
+ (`order_info::calculate_partial_fill_balances`, `state::process_fills`, `fill.move`):
500
+ - bid taker: `floor(taker × matched quote)` once on the aggregate, on top of the quote;
501
+ - bid maker: `floor(maker × resting quote)` escrowed at placement; on cancel / modify-down /
502
+ expiry the class's `cancel_retention_bps` share of the released escrow is kept;
503
+ - ask taker: `floor(taker × matched quote)` out of the proceeds;
504
+ - ask maker: `floor(maker × fill quote)` per fill, out of the proceeds.
505
+ - **Bid deposit:** `computeBidQuoteDeposit(price, qty, rate)` = `notional + floor(notional ×
506
+ rate / 1e9)`. The facade passes `TradingFees.bidEscrowFeeRate` = max(tier-0 taker, tier-0
507
+ maker) over the active and staged ladders — tier 0 bounds every tier, and floors are
508
+ subadditive, so the deposit covers any match/rest split at any tier, even across an epoch
509
+ boundary. Surplus stays in the trading account. Asks deposit only items.
510
+ - **Quotes:** `estimateMarketBuyCost` (asks low-first, fee on the aggregate — exact for an
511
+ unchanged book, so the old per-fill rounding buffer is gone; `marketBuyRoundingBuffer` is
512
+ deprecated), `estimateMarketSellProceeds` (bids high-first, net of the taker fee),
513
+ `computeAskProceeds`. Use the account's own rate from `orders.fees()`. Pool metadata's
514
+ `fee` is only the entry-tier taker rate (no maker rate, no tiers). Market bids require an
515
+ explicit `quoteBudget`; a market order fills at most 100 makers (`MAX_FILLS`).
386
516
  - **Decimals:** CRED and each item type carry `decimals` (pool metadata's
387
517
  `base_asset_decimals` / `quote_asset_decimals`); the SDK exposes both `bigint` base-unit
388
518
  and helper `toBase(human, decimals)` / `fromBase(base, decimals)`.
@@ -404,12 +534,13 @@ back into the SSU/hangar (needs `ssu`, `character`, `vaultConfig`, `collection`,
404
534
  excluded**. The current stillness trading IDs (for the SDK `testnet` preset):
405
535
 
406
536
  ```
407
- triexPackageId 0x291b9da738dffedd18d7c5049e5e6792270202e03f3c9d9db4c7097670bf6eb2
408
- triexRegistryId 0x14a58f254b8243bf3f74c057d81cc310498b3d0fe3781650ad58405d7e5f17e4 (shared obj — not in /package-ids)
409
- multicoinPackageId 0x99a4c039477ac7e7affcb5a5609dd23c29ab69e9436e740d94a4e168c2506cfb
410
- warehouseReceiptsPackageId 0x0c9d4414aa12eaa1ebf9d32437c1e6403fbf941ffcdca5b9ca16d1769313417e
411
- credCoinType 0xfbcbd9155669e157ce3999e073930b4c4b67255c3cf88d0d80c76342a31e6710::cred::CRED
412
- worldPackageId 0x8b8a46ed766fa1358ce7c5c51f6a164b13d627a63e45343f69ed0ba0446c1aa1 (= worldOriginalPackageId)
537
+ triexPackageId 0xdbf259ed33d70666379492199137e2ad50fcc1ed1e56acd5780329f7fe982945 (cycle 7 + TRIEX-158 fresh publish; = original id)
538
+ triexRegistryId 0x2f37ad133427cabf94653be9a67560f87ea7458eaef2b410f6497808cef722c2 (shared obj — not in /package-ids)
539
+ triexFeePolicyId 0xcd63402799fe6b3a3ff2d963f90449e843f178659c349a08a1503db516163748 (shared obj — not in /package-ids)
540
+ multicoinPackageId 0xdbb778cba30e7deccf61169fbfbcd10a867654e1e2822facd789a99bd2c4e2ba
541
+ warehouseReceiptsPackageId 0x134dfa96ad8bc50d4a2055cd78c91e264feb2fe79facf2d030f8bb466a80bb68
542
+ credCoinType 0xfbcbd9155669e157ce3999e073930b4c4b67255c3cf88d0d80c76342a31e6710::cred::CRED (never republished)
543
+ worldPackageId 0x7be18d6294e533bedd9a5d70a96ce8d9d4b87a7c74188ba65d3fe966bbed9d92 (= worldOriginalPackageId)
413
544
  clock 0x6
414
545
  ```
415
546
 
@@ -451,7 +582,8 @@ via `packageIds`. Optionally hydrate the *package* IDs at runtime from
451
582
  keypair), semantic-release → publish `@trinaryex/sdk@0.x`.
452
583
  - **Later (post-MVP):** full sweep-all convenience (multi-hub redeem), tribe/governance
453
584
  trading, **gas-station sponsored (gasless) execution** (optional `sponsor` hook), live
454
- orderbook streaming, mainnet, coin-pools (currency-pair markets). (Cancel/modify and
585
+ orderbook streaming, mainnet. Coin-pools (currency-pair markets) landed in cycle 7 —
586
+ DESIGN-COINS.md. (Cancel/modify and
455
587
  claim-settled were pulled INTO the MVP — a trading bot is not viable without them.)
456
588
 
457
589
  ---
@@ -484,7 +616,9 @@ via `packageIds`. Optionally hydrate the *package* IDs at runtime from
484
616
  (borrow_owner_cap → receipt::deposit_for_receipt → return_owner_cap → deposit_multicoin);
485
617
  see §6.1.
486
618
  - **OQ-5 — Ignore coin-pools.** MVP trades **item↔CRED** via `multicoin_pool` ("pools" family
487
- only). Coin-pools deferred.
619
+ only). Coin-pools deferred. **Lifted (cycle 7):** coin pools merged into the main `triex`
620
+ package and are traded through `client.coins` — fullnode reads (the coin-pool indexer
621
+ routes are not on the gateway) plus `GET /v1/coins`; see DESIGN-COINS.md.
488
622
  - **OQ-6 — Always v1.** SDK supports only the newest contracts; no version branching (§7).
489
623
  - **OQ-7 — Gas:** left to the caller's `executor`; sponsored/gasless is a **post-MVP** feature
490
624
  (optional `sponsor` hook).
@@ -558,40 +692,88 @@ object IDs. For latency-sensitive bots, note the post-MVP live-stream option.
558
692
 
559
693
  ---
560
694
 
561
- ## 13. Appendix — current `etl-api` gateway route inventory
695
+ ## 13. Appendix — published gateway surface → SDK
562
696
 
563
- From `dynamic-config-registry/gateway-routes/etl-api.json` (65 routes), **as of 2026-08-21**
564
- (DCR `62acb22` + `feat/publish-inventory-balances`). Phase 0 is done: everything the MVP
565
- reads is live.
566
-
567
- **Enabled — MVP reads** (CU costs as published):
697
+ The gateway's published OpenAPI document (`https://api.trinary.exchange/swagger.json`,
698
+ vendored at `test/fixtures/gateway-openapi.json`) — **60 operations, all `GET`**, as of
699
+ 2026-10-01. Upstreams per `dynamic-config-registry/gateway-routes/`: etl-api (market
700
+ data), location-api (`/v1/spatial/*`, `/v1/routing/*`) and the public CDN
701
+ (`/v1/world/items`, `/v1/world/recipes*`). `npm run check:gateway [-- --live]` lists any
702
+ operation the SDK does not wrap.
568
703
 
569
704
  ```
570
- GET /v1/discovery (150 CU) # story 6 — repriced 30 → 150 (2 rps sustained on free/standard)
571
- GET /v1/inventory/balances (50 CU) # story 2 (items only; hub-scoped)
572
- GET /v1/hubs/{hub_id}/vault (20 CU) # stories 4,7,9 (vaultConfig/collection)
573
- GET /v1/hubs/{hub_id}/location (20 CU) # story 7 (location/owner/visibility)
574
- GET /v1/hubs/{hub_id}/items (20 CU) # story 8
575
- GET /v1/collections/{collection_id}/hub (20 CU) # story 7 reverse lookup
576
- GET /v1/pools/resolve (30 CU) # stories 9,10,11 (collection+asset → poolId)
577
- GET /v1/pools/{pool_id}/orderbook (30 CU) # story 9
578
- GET /v1/pools/{pool_id}/metadata (20 CU) # stories 10,11 (1e9-scaled fee)
579
- GET /v1/balance-managers/{balance_manager_id}/open-orders (30 CU) # story 14
580
- GET /v1/balance-managers/{balance_manager_id}/fills (30 CU) # story 14
581
- GET /v1/balance-managers/{balance_manager_id}/trades (30 CU) # story 14
705
+ # Market data (etl-api)
706
+ GET /v1/discovery 150 CU market.discover · iterateDiscovery
707
+ GET /v1/pools/resolve 30 CU market.resolvePool (internal: indexer.resolvePool)
708
+ GET /v1/pools/{pool_id}/orderbook 30 CU indexer.orderbook
709
+ GET /v1/pools/{pool_id}/metadata 20 CU market.poolMetadata
710
+ GET /v1/pools/{pool_id}/orders/{order_id} 30 CU orders.get · ro.order
711
+ GET /v1/pools/top-by-fees 50 CU market.topPoolsByFees
712
+ GET /v1/hubs/{hub_id}/items/{asset_id}/orderbook 50 CU market.orderbook
713
+ GET /v1/fills/{event_digest} 30 CU orders.fill · ro.fill
714
+ GET /v1/trades/recent 50 CU market.recentTrades · iterateRecentTrades
715
+ GET /v1/display-prices 50 CU market.displayPrices
716
+ GET /v1/display-prices/{item_id} 20 CU market.displayPrice
717
+ GET /v1/stats 50 CU market.stats
718
+ # Hubs & locations (etl-api)
719
+ GET /v1/hubs/{hub_id}/vault 20 CU market.hub (+ write flows)
720
+ GET /v1/hubs/{hub_id}/location 20 CU market.hub
721
+ GET /v1/hubs/{hub_id}/items 20 CU market.itemsAtHub
722
+ GET /v1/hubs/locations 50 CU market.hubLocations · iterateHubLocations
723
+ GET /v1/hubs/enriched 50 CU market.hubsEnriched
724
+ GET /v1/hubs/economics 50 CU market.hubEconomics
725
+ GET /v1/hubs/{hub_id}/nearby 50 CU market.nearbyHubs
726
+ GET /v1/hubs/nearby-by-system 50 CU market.nearbyHubsBySystem
727
+ GET /v1/items/{item_id}/locations 50 CU market.itemLocations · iterateItemLocations
728
+ GET /v1/collections/{collection_id}/hub 20 CU indexer.collectionHub
729
+ GET /v1/assemblies/owners 50 CU market.assemblyOwners
730
+ GET /v1/assemblies/enriched 50 CU market.assembliesEnriched
731
+ GET /v1/solar-systems/names 50 CU market.solarSystemNames
732
+ # Trading accounts (etl-api)
733
+ GET /v1/inventory/balances 50 CU balances.atHub
734
+ GET /v1/trading-accounts/{id}/open-orders 30 CU orders.openOrders · iterateOpenOrders
735
+ GET /v1/trading-accounts/{id}/fills 30 CU orders.fills · iterateFills
736
+ GET /v1/trading-accounts/{id}/trades 30 CU orders.trades · iterateTrades
737
+ GET /v1/trading-accounts/{id}/sweepable 30 CU account.sweepable / claimSettled
738
+ GET /v1/trading-accounts/owners 50 CU account.owners
739
+ # Characters, tribes & world (etl-api + public CDN)
740
+ GET /v1/characters/{object_id} 20 CU characters.get
741
+ GET /v1/characters/address/{address} 20 CU characters.byAddress
742
+ GET /v1/characters/name/{name} 20 CU characters.byName
743
+ GET /v1/characters/batch 50 CU characters.batch
744
+ GET /v1/world/tribes/{tribe_id} 20 CU characters.tribe
745
+ GET /v1/tribes/{tribe_id} 20 CU — deprecated 2026-08-20 alias of the above; not wrapped
746
+ GET /v1/world/items 20 CU world.items (CDN)
747
+ GET /v1/world/items/{asset_id} 20 CU world.item
748
+ GET /v1/world/items/search 50 CU market.searchItems
749
+ GET /v1/world/recipes 20 CU world.recipes (CDN)
750
+ GET /v1/world/recipes/{item_id} 20 CU world.recipesFor (CDN)
751
+ # Star map & routing (location-api)
752
+ GET /v1/spatial/systems/{solar_system} 20 CU spatial.system
753
+ GET /v1/spatial/systems 50 CU spatial.systems
754
+ GET /v1/spatial/systems/{solar_system}/nearby 50 CU spatial.nearbySystems
755
+ GET /v1/spatial/coordinates/nearby 50 CU spatial.systemsNearCoordinates
756
+ GET /v1/spatial/systems/search/autocomplete 20 CU spatial.autocompleteSystems
757
+ GET /v1/spatial/stats 20 CU spatial.stats
758
+ GET /v1/routing/route 100 CU routing.route
759
+ GET /v1/routing/compare 300 CU routing.compare
760
+ GET /v1/routing/stats 20 CU routing.stats
761
+ # Organizations (etl-api) — Armature module, DESIGN-ARMATURE.md §13.1
762
+ GET /v1/orgs · /v1/orgs/{org_id} · /v1/orgs/directory · /v1/orgs/{org_id}/proposals
763
+ GET /v1/players/{address}/orgs · /v1/players/{address}/accessible-keyspaces
764
+ GET /v1/hubs/{hub_id}/dao-vaults · /v1/search
765
+ # Coin (currency-pair) markets — coin-pool module
766
+ GET /v1/coins
582
767
  ```
583
768
 
584
- (Also enabled beyond MVP need: `/v1/balance-managers/{bm}/sweepable`, `/owners`, the wider
585
- pools/hubs/assets/activity families, `GET /v1/characters/*`, `GET /v1/orgs*`,
586
- `GET /v1/search` (100 CU), `GET /v1/stats`, `GET /v1/trades/recent`.)
587
-
588
- **Deliberately disabled (D11 — sluice attaches no caller identity):** the remaining
769
+ **Deliberately unpublished (D11 — sluice attaches no caller identity):** the remaining
589
770
  `inventory` family, including the seven POST container mutations and two GETs the SDK
590
771
  replaces with fullnode reads — `GET /v1/inventory/balance-manager` (story 1: on-chain
591
772
  `listOwnedObjects` is authoritative) and `GET /v1/inventory/receipt-objects` (story 4:
592
- wallet-receipt discovery is PTB input resolution). `coin-pools/*` stays disabled by choice.
773
+ wallet-receipt discovery is PTB input resolution). `coin-pools/*` stays disabled by choice —
774
+ the `coins` group reads coin pools from the fullnode instead and uses only `GET /v1/coins`.
593
775
 
594
776
  > Remember a merge to `main` does **not** update the running gateway — run
595
- > `scripts/push-gateway.sh --env <env>` (per CLAUDE.md). The balances route is already
596
- > published; its branch still needs merging to `main` so a future publish from a clean
597
- > checkout doesn't revert it.
777
+ > `scripts/push-gateway.sh --env <env>` in dynamic-config-registry. location-api's
778
+ > cycle-7 names change (nullable `solar_system_name`, `known_solar_system_names`) is
779
+ > deployed but not yet reflected in the published spec; the SDK accepts both shapes.