@raintree-technology/perps 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.4] - 2026-02-26
11
+
12
+ ### Added
13
+ - Expanded advanced `order` command surface with direct adapter-backed workflows, including batch placement/cancel, cancel-all-after, MMP, TWAP, isolated margin adjustment, and read commands (`get`, `open`, `trades`, `position`, `history`, `funding-history`, `public-trades`).
14
+ - Added first-class `markets` read commands for `get`, `ticker`, `tickers`, `funding-rate`, and `funding-rates`.
15
+ - Added Decibel REST auth-header regression tests and websocket protocol coverage tests.
16
+ - Added `specs/perp-parity/*` and `scripts/ops/ralph-perp-parity-loop.sh` to support systematic exchange parity execution loops.
17
+
18
+ ### Changed
19
+ - Updated Decibel, Hyperliquid, Aevo, Orderly, and Paradex adapter/command parity wiring to expose only direct, capability-backed behavior.
20
+ - Updated docs and operator guidance (`README`, `CAPABILITIES`, exchange docs, release notes) to match the current command surface and Decibel auth behavior.
21
+ - Updated website static endpoint delivery to use `public/` assets and refreshed marketing shell components.
22
+
23
+ ### Fixed
24
+ - Fixed Decibel REST auth semantics so Authorization headers are only attached where required and aligned `trades` endpoint handling with authenticated API behavior.
25
+ - Restored command registration and test coverage for newly added `order` and `markets` subcommands.
26
+
10
27
  ## [0.1.2] - 2026-02-26
11
28
 
12
29
  ### Added
@@ -71,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
71
88
  - Risk management: limits, drawdown, position sizing
72
89
  - Execution journal and safety checks
73
90
 
74
- [Unreleased]: https://github.com/raintree-technology/perps/compare/v0.1.2...HEAD
91
+ [Unreleased]: https://github.com/raintree-technology/perps/compare/v0.1.4...HEAD
92
+ [0.1.4]: https://github.com/raintree-technology/perps/releases/tag/v0.1.4
75
93
  [0.1.2]: https://github.com/raintree-technology/perps/releases/tag/v0.1.2
76
94
  [0.1.1]: https://github.com/raintree-technology/perps/releases/tag/v0.1.1
77
95
  [0.1.0]: https://github.com/raintree-technology/perps/releases/tag/v0.1.0
package/README.md CHANGED
@@ -133,8 +133,8 @@ See **[CAPABILITIES.md](CAPABILITIES.md)** for the full reference — every comm
133
133
  ## Command Surface
134
134
 
135
135
  ```bash
136
- perps markets ls --help # List markets with funding, OI, volume
137
- perps order --help # Place orders (market, limit, stop, take-profit)
136
+ perps markets --help # Market data + read commands (ls/get/ticker/tickers/funding-rate/funding-rates)
137
+ perps order --help # Order lifecycle + advanced reads (get/open/trades/position/history/public-trades)
138
138
  perps arb --help # Cross-exchange arbitrage
139
139
  perps account --help # Portfolio (positions, balances)
140
140
  perps agent --help # HTTP agent gateway
@@ -152,6 +152,16 @@ perps doctor # Health check
152
152
 
153
153
  Every command supports `--json` for machine-readable output and `--help` for usage details.
154
154
 
155
+ Advanced order commands are feature-gated per exchange and exposed directly when supported:
156
+
157
+ ```bash
158
+ perps order batch-place --help
159
+ perps order batch-cancel --help
160
+ perps order cancel-all-after --help
161
+ perps order mmp --help
162
+ perps order twap --help
163
+ ```
164
+
155
165
  Agent-friendly mode:
156
166
 
157
167
  ```bash
@@ -5,7 +5,7 @@
5
5
  * API Docs: https://docs.aevo.xyz/
6
6
  * Base URL: https://api.aevo.xyz
7
7
  */
8
- import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type ModifyOrderParams, type FundingPayment, type PublicTrade, type SubscriptionCallbacks, type Unsubscribe, type MarginType, type MMPConfig, type MMPStatus, type TWAPParams, type TWAPStatus } from "./interface.js";
8
+ import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type ModifyOrderParams, type FundingPayment, type PublicTrade, type SubscriptionCallbacks, type Unsubscribe, type MarginType, type MMPConfig, type MMPStatus } from "./interface.js";
9
9
  export declare class AevoAdapter implements PerpDEXAdapter {
10
10
  readonly info: ExchangeInfo;
11
11
  private baseUrl;
@@ -41,17 +41,12 @@ export declare class AevoAdapter implements PerpDEXAdapter {
41
41
  modifyOrder(params: ModifyOrderParams): Promise<Order>;
42
42
  batchPlaceOrders(paramsList: OrderParams[]): Promise<Order[]>;
43
43
  batchCancelOrders(paramsList: CancelOrderParams[]): Promise<boolean[]>;
44
- cancelAllAfter(_timeoutMs: number): Promise<void>;
45
44
  getOrderHistory(market?: string, limit?: number): Promise<Order[]>;
46
45
  getFundingHistory(market?: string, limit?: number): Promise<FundingPayment[]>;
47
46
  getPublicTrades(market: string, limit?: number): Promise<PublicTrade[]>;
48
47
  setMMP(config: MMPConfig): Promise<void>;
49
48
  getMMP(market: string): Promise<MMPStatus>;
50
49
  resetMMP(market: string): Promise<void>;
51
- placeTWAP(_params: TWAPParams): Promise<TWAPStatus>;
52
- cancelTWAP(_twapId: string): Promise<boolean>;
53
- getTWAPStatus(_twapId: string): Promise<TWAPStatus | null>;
54
- updateIsolatedMargin(_market: string, _amount: string): Promise<void>;
55
50
  subscribe(callbacks: SubscriptionCallbacks): Unsubscribe;
56
51
  subscribeOrderBook(market: string, callback: (book: OrderBook) => void): Unsubscribe;
57
52
  subscribeTicker(market: string, callback: (ticker: Ticker) => void): Unsubscribe;
@@ -641,9 +641,6 @@ export class AevoAdapter {
641
641
  }
642
642
  return results;
643
643
  }
644
- async cancelAllAfter(_timeoutMs) {
645
- throw new Error("Aevo does not support cancelAllAfter (dead man's switch)");
646
- }
647
644
  async getOrderHistory(market, limit = 100) {
648
645
  this.ensureAuth();
649
646
  const query = new URLSearchParams();
@@ -731,24 +728,6 @@ export class AevoAdapter {
731
728
  });
732
729
  }
733
730
  // --------------------------------------------------------------------------
734
- // TWAP Orders
735
- // --------------------------------------------------------------------------
736
- async placeTWAP(_params) {
737
- throw new Error("Aevo TWAP orders are not supported through this adapter");
738
- }
739
- async cancelTWAP(_twapId) {
740
- throw new Error("Aevo TWAP orders are not supported through this adapter");
741
- }
742
- async getTWAPStatus(_twapId) {
743
- throw new Error("Aevo TWAP orders are not supported through this adapter");
744
- }
745
- // --------------------------------------------------------------------------
746
- // Margin Management
747
- // --------------------------------------------------------------------------
748
- async updateIsolatedMargin(_market, _amount) {
749
- throw new Error("Aevo does not support isolated margin adjustment");
750
- }
751
- // --------------------------------------------------------------------------
752
731
  // Subscriptions (polling)
753
732
  // --------------------------------------------------------------------------
754
733
  subscribe(callbacks) {
@@ -10,12 +10,57 @@ const CAPABILITY_CATEGORIES = [
10
10
  "advancedTrading",
11
11
  ];
12
12
  const CATEGORY_METHODS = {
13
- marketData: ["getMarkets", "getTicker", "getOrderBook", "getFundingRate", "getPublicTrades"],
14
- authenticatedReads: ["getPositions", "getOrders", "getBalances", "getTrades", "getOrderHistory", "getFundingHistory"],
15
- orderLifecycle: ["placeOrder", "getOrder", "getOrders", "modifyOrder"],
16
- orderCancellation: ["cancelOrder", "cancelAllOrders", "cancelAllAfter"],
17
- subscriptions: ["subscribe", "subscribeOrderBook", "subscribeTicker"],
18
- advancedTrading: ["modifyOrder", "batchPlaceOrders", "batchCancelOrders", "cancelAllAfter", "getOrderHistory", "getFundingHistory", "getPublicTrades", "setMMP", "getMMP", "resetMMP", "placeTWAP", "cancelTWAP", "getTWAPStatus", "updateIsolatedMargin"],
13
+ marketData: [
14
+ { method: "getMarkets" },
15
+ { method: "getTicker" },
16
+ { method: "getOrderBook" },
17
+ { method: "getFundingRate" },
18
+ { method: "getPublicTrades", when: (adapter) => adapter.info.features.publicTrades },
19
+ ],
20
+ authenticatedReads: [
21
+ { method: "getPositions" },
22
+ { method: "getOrders" },
23
+ { method: "getBalances" },
24
+ { method: "getTrades" },
25
+ { method: "getOrderHistory", when: (adapter) => adapter.info.features.orderHistory },
26
+ { method: "getFundingHistory", when: (adapter) => adapter.info.features.fundingHistory },
27
+ ],
28
+ orderLifecycle: [
29
+ { method: "placeOrder" },
30
+ { method: "getOrder" },
31
+ { method: "getOrders" },
32
+ { method: "modifyOrder", when: (adapter) => adapter.info.features.modifyOrders },
33
+ ],
34
+ orderCancellation: [
35
+ { method: "cancelOrder" },
36
+ { method: "cancelAllOrders" },
37
+ { method: "cancelAllAfter", when: (adapter) => adapter.info.features.cancelAllAfter },
38
+ ],
39
+ subscriptions: [
40
+ { method: "subscribe" },
41
+ { method: "subscribeOrderBook" },
42
+ { method: "subscribeTicker" },
43
+ ],
44
+ advancedTrading: [
45
+ { method: "modifyOrder", when: (adapter) => adapter.info.features.modifyOrders },
46
+ { method: "batchPlaceOrders", when: (adapter) => adapter.info.features.batchOrders },
47
+ { method: "batchCancelOrders", when: (adapter) => adapter.info.features.batchOrders },
48
+ { method: "cancelAllAfter", when: (adapter) => adapter.info.features.cancelAllAfter },
49
+ { method: "getOrderHistory", when: (adapter) => adapter.info.features.orderHistory },
50
+ { method: "getFundingHistory", when: (adapter) => adapter.info.features.fundingHistory },
51
+ { method: "getPublicTrades", when: (adapter) => adapter.info.features.publicTrades },
52
+ { method: "setMMP", when: (adapter) => adapter.info.features.mmp },
53
+ { method: "getMMP", when: (adapter) => adapter.info.features.mmp },
54
+ { method: "resetMMP", when: (adapter) => adapter.info.features.mmp },
55
+ { method: "placeTWAP", when: (adapter) => adapter.info.features.twapOrders },
56
+ { method: "cancelTWAP", when: (adapter) => adapter.info.features.twapOrders },
57
+ {
58
+ method: "getTWAPStatus",
59
+ when: (adapter) => adapter.info.features.twapOrders &&
60
+ typeof adapter.getTWAPStatus === "function",
61
+ },
62
+ { method: "updateIsolatedMargin", when: (adapter) => adapter.info.features.isolatedMargin },
63
+ ],
19
64
  };
20
65
  function methodLooksImplemented(method) {
21
66
  if (typeof method !== "function") {
@@ -40,10 +85,11 @@ function getCapabilityLevel(implemented, total) {
40
85
  return "partial";
41
86
  }
42
87
  function certifyCategory(adapter, category) {
43
- const methods = CATEGORY_METHODS[category].map((methodName) => {
44
- const probe = methodLooksImplemented(adapter[methodName]);
88
+ const applicableMethods = CATEGORY_METHODS[category].filter((spec) => !spec.when || spec.when(adapter));
89
+ const methods = applicableMethods.map((spec) => {
90
+ const probe = methodLooksImplemented(adapter[spec.method]);
45
91
  return {
46
- method: methodName,
92
+ method: spec.method,
47
93
  implemented: probe.implemented,
48
94
  reason: probe.reason,
49
95
  };
@@ -24,10 +24,44 @@ export interface PlaceDecibelOrderParams {
24
24
  builderAddr?: string;
25
25
  builderFeeBps?: number;
26
26
  }
27
+ export interface UpdateDecibelOrderParams {
28
+ market: DecibelExecutionMarket;
29
+ orderId?: string;
30
+ clientOrderId?: string;
31
+ side: "buy" | "sell";
32
+ price: number;
33
+ sizeUnits: number;
34
+ timeInForce?: 0 | 1 | 2;
35
+ reduceOnly?: boolean;
36
+ stopPrice?: number;
37
+ tpTriggerPrice?: number;
38
+ tpLimitPrice?: number;
39
+ slTriggerPrice?: number;
40
+ slLimitPrice?: number;
41
+ builderAddr?: string;
42
+ builderFeeBps?: number;
43
+ }
44
+ export interface PlaceDecibelTwapParams {
45
+ market: DecibelExecutionMarket;
46
+ side: "buy" | "sell";
47
+ sizeUnits: number;
48
+ reduceOnly?: boolean;
49
+ clientOrderId?: string;
50
+ twapFrequencySeconds: number;
51
+ twapDurationSeconds: number;
52
+ builderAddr?: string;
53
+ builderFeeBps?: number;
54
+ }
55
+ export interface PlaceDecibelTwapResult {
56
+ txHash: string;
57
+ orderId: string | null;
58
+ }
27
59
  export interface DecibelOrderManagerConfig {
28
60
  fullnodeUrl: string;
29
61
  network: "testnet" | "mainnet";
30
62
  packageAddress: string;
63
+ usdcAddress: string;
64
+ usdcDecimals?: number;
31
65
  privateKey: Hex;
32
66
  subaccountAddress: string;
33
67
  }
@@ -35,11 +69,18 @@ export declare class DecibelOrderManager {
35
69
  private readonly aptos;
36
70
  private readonly account;
37
71
  private readonly packageAddress;
72
+ private readonly usdcAddress;
73
+ private readonly usdcDecimals;
38
74
  private subaccountAddress;
39
75
  constructor(config: DecibelOrderManagerConfig);
40
76
  setSubaccount(addr: string): void;
41
77
  placeOrder(params: PlaceDecibelOrderParams): Promise<string>;
42
78
  cancelOrder(orderId: string, marketAddr: string): Promise<string>;
43
79
  cancelAllOrders(marketAddr: string): Promise<string>;
80
+ updateOrder(params: UpdateDecibelOrderParams): Promise<string>;
44
81
  configureUserSettingsForMarket(marketAddr: string, isCross: boolean, userLeverageBps: number): Promise<string>;
82
+ updateIsolatedPositionMargin(marketAddr: string, amount: number): Promise<string>;
83
+ placeTwapOrder(params: PlaceDecibelTwapParams): Promise<PlaceDecibelTwapResult>;
84
+ cancelTwapOrder(orderId: string, marketAddr: string): Promise<string>;
85
+ private extractOrderIdFromTransaction;
45
86
  }
@@ -5,6 +5,8 @@ export class DecibelOrderManager {
5
5
  aptos;
6
6
  account;
7
7
  packageAddress;
8
+ usdcAddress;
9
+ usdcDecimals;
8
10
  subaccountAddress;
9
11
  constructor(config) {
10
12
  const aptosConfig = new AptosConfig({
@@ -16,6 +18,8 @@ export class DecibelOrderManager {
16
18
  privateKey: new Ed25519PrivateKey(config.privateKey),
17
19
  });
18
20
  this.packageAddress = config.packageAddress;
21
+ this.usdcAddress = config.usdcAddress;
22
+ this.usdcDecimals = config.usdcDecimals ?? 6;
19
23
  this.subaccountAddress = config.subaccountAddress;
20
24
  }
21
25
  setSubaccount(addr) {
@@ -100,6 +104,74 @@ export class DecibelOrderManager {
100
104
  log.info("Decibel cancel-all submitted", { txHash: pending.hash, marketAddr });
101
105
  return pending.hash;
102
106
  }
107
+ async updateOrder(params) {
108
+ const { market, orderId, clientOrderId, side, price, sizeUnits, timeInForce = 0, reduceOnly = false, stopPrice, tpTriggerPrice, tpLimitPrice, slTriggerPrice, slLimitPrice, builderAddr, builderFeeBps, } = params;
109
+ if (!orderId && !clientOrderId) {
110
+ throw new Error("Decibel updateOrder requires orderId or clientOrderId");
111
+ }
112
+ const chainPrice = formatPrice(price, market);
113
+ const chainSize = formatSize(sizeUnits, market);
114
+ const isBuy = side === "buy";
115
+ const payload = {
116
+ function: orderId
117
+ ? `${this.packageAddress}::dex_accounts_entry::update_order_to_subaccount`
118
+ : `${this.packageAddress}::dex_accounts_entry::update_client_order_to_subaccount`,
119
+ typeArguments: [],
120
+ functionArguments: orderId
121
+ ? [
122
+ this.subaccountAddress,
123
+ orderId,
124
+ market.marketAddr,
125
+ chainPrice.toString(),
126
+ chainSize.toString(),
127
+ isBuy,
128
+ timeInForce,
129
+ reduceOnly,
130
+ stopPrice != null ? formatPrice(stopPrice, market).toString() : null,
131
+ tpTriggerPrice != null ? formatPrice(tpTriggerPrice, market).toString() : null,
132
+ tpLimitPrice != null ? formatPrice(tpLimitPrice, market).toString() : null,
133
+ slTriggerPrice != null ? formatPrice(slTriggerPrice, market).toString() : null,
134
+ slLimitPrice != null ? formatPrice(slLimitPrice, market).toString() : null,
135
+ builderAddr ?? null,
136
+ builderFeeBps ?? null,
137
+ ]
138
+ : [
139
+ this.subaccountAddress,
140
+ clientOrderId,
141
+ market.marketAddr,
142
+ chainPrice.toString(),
143
+ chainSize.toString(),
144
+ isBuy,
145
+ timeInForce,
146
+ reduceOnly,
147
+ stopPrice != null ? formatPrice(stopPrice, market).toString() : null,
148
+ tpTriggerPrice != null ? formatPrice(tpTriggerPrice, market).toString() : null,
149
+ tpLimitPrice != null ? formatPrice(tpLimitPrice, market).toString() : null,
150
+ slTriggerPrice != null ? formatPrice(slTriggerPrice, market).toString() : null,
151
+ slLimitPrice != null ? formatPrice(slLimitPrice, market).toString() : null,
152
+ builderAddr ?? null,
153
+ builderFeeBps ?? null,
154
+ ],
155
+ };
156
+ const tx = await this.aptos.transaction.build.simple({
157
+ sender: this.account.accountAddress,
158
+ data: payload,
159
+ });
160
+ const pending = await this.aptos.signAndSubmitTransaction({
161
+ signer: this.account,
162
+ transaction: tx,
163
+ });
164
+ log.info("Decibel order update submitted", {
165
+ txHash: pending.hash,
166
+ orderId,
167
+ clientOrderId,
168
+ market: market.marketName,
169
+ side,
170
+ price,
171
+ size: sizeUnits,
172
+ });
173
+ return pending.hash;
174
+ }
103
175
  async configureUserSettingsForMarket(marketAddr, isCross, userLeverageBps) {
104
176
  const leverageBps = Math.max(1, Math.round(userLeverageBps));
105
177
  const payload = {
@@ -123,6 +195,150 @@ export class DecibelOrderManager {
123
195
  });
124
196
  return pending.hash;
125
197
  }
198
+ async updateIsolatedPositionMargin(marketAddr, amount) {
199
+ if (!Number.isFinite(amount) || amount === 0) {
200
+ throw new Error("Decibel isolated margin update requires a non-zero amount");
201
+ }
202
+ const isDeposit = amount > 0;
203
+ const amountUnits = decimalToChainUnits(Math.abs(amount), this.usdcDecimals);
204
+ const payload = {
205
+ function: `${this.packageAddress}::dex_accounts_entry::transfer_collateral_to_isolated_position`,
206
+ typeArguments: [],
207
+ functionArguments: [
208
+ this.subaccountAddress,
209
+ marketAddr,
210
+ isDeposit,
211
+ this.usdcAddress,
212
+ amountUnits.toString(),
213
+ ],
214
+ };
215
+ const tx = await this.aptos.transaction.build.simple({
216
+ sender: this.account.accountAddress,
217
+ data: payload,
218
+ });
219
+ const pending = await this.aptos.signAndSubmitTransaction({
220
+ signer: this.account,
221
+ transaction: tx,
222
+ });
223
+ log.info("Decibel isolated margin update submitted", {
224
+ txHash: pending.hash,
225
+ marketAddr,
226
+ amount,
227
+ usdcAddress: this.usdcAddress,
228
+ });
229
+ return pending.hash;
230
+ }
231
+ async placeTwapOrder(params) {
232
+ const { market, side, sizeUnits, reduceOnly = false, clientOrderId, twapFrequencySeconds, twapDurationSeconds, builderAddr, builderFeeBps, } = params;
233
+ const isBuy = side === "buy";
234
+ const chainSize = formatSize(sizeUnits, market);
235
+ const payload = {
236
+ function: `${this.packageAddress}::dex_accounts_entry::place_twap_order_to_subaccount_v2`,
237
+ typeArguments: [],
238
+ functionArguments: [
239
+ this.subaccountAddress,
240
+ market.marketAddr,
241
+ chainSize.toString(),
242
+ isBuy,
243
+ reduceOnly,
244
+ clientOrderId ?? null,
245
+ twapFrequencySeconds,
246
+ twapDurationSeconds,
247
+ builderAddr ?? null,
248
+ builderFeeBps ?? null,
249
+ ],
250
+ };
251
+ const tx = await this.aptos.transaction.build.simple({
252
+ sender: this.account.accountAddress,
253
+ data: payload,
254
+ });
255
+ const pending = await this.aptos.signAndSubmitTransaction({
256
+ signer: this.account,
257
+ transaction: tx,
258
+ });
259
+ const orderId = await this.extractOrderIdFromTransaction(pending.hash);
260
+ log.info("Decibel TWAP order submitted", {
261
+ txHash: pending.hash,
262
+ orderId,
263
+ market: market.marketName,
264
+ side,
265
+ size: sizeUnits,
266
+ reduceOnly,
267
+ twapFrequencySeconds,
268
+ twapDurationSeconds,
269
+ });
270
+ return {
271
+ txHash: pending.hash,
272
+ orderId,
273
+ };
274
+ }
275
+ async cancelTwapOrder(orderId, marketAddr) {
276
+ const payload = {
277
+ function: `${this.packageAddress}::dex_accounts_entry::cancel_twap_orders_to_subaccount`,
278
+ typeArguments: [],
279
+ functionArguments: [this.subaccountAddress, marketAddr, orderId],
280
+ };
281
+ const tx = await this.aptos.transaction.build.simple({
282
+ sender: this.account.accountAddress,
283
+ data: payload,
284
+ });
285
+ const pending = await this.aptos.signAndSubmitTransaction({
286
+ signer: this.account,
287
+ transaction: tx,
288
+ });
289
+ log.info("Decibel TWAP cancel submitted", { txHash: pending.hash, orderId, marketAddr });
290
+ return pending.hash;
291
+ }
292
+ async extractOrderIdFromTransaction(txHash) {
293
+ try {
294
+ await this.aptos.waitForTransaction({
295
+ transactionHash: txHash,
296
+ options: { checkSuccess: false },
297
+ });
298
+ }
299
+ catch {
300
+ // The transaction may still be queryable by hash even if wait times out.
301
+ }
302
+ try {
303
+ const tx = await this.aptos.getTransactionByHash({ transactionHash: txHash });
304
+ const events = tx.events;
305
+ if (!Array.isArray(events))
306
+ return null;
307
+ for (const event of events) {
308
+ if (!event || typeof event !== "object")
309
+ continue;
310
+ const eventType = event.type;
311
+ if (typeof eventType !== "string")
312
+ continue;
313
+ if (!eventType.includes("TwapEvent") && !eventType.includes("OrderEvent"))
314
+ continue;
315
+ const data = event.data;
316
+ if (!data || typeof data !== "object")
317
+ continue;
318
+ const user = data.user;
319
+ const account = data.account;
320
+ if (user !== this.subaccountAddress && account !== this.subaccountAddress)
321
+ continue;
322
+ const rawOrderId = data.order_id;
323
+ if (typeof rawOrderId === "string")
324
+ return rawOrderId;
325
+ if (typeof rawOrderId === "number" || typeof rawOrderId === "bigint") {
326
+ return String(rawOrderId);
327
+ }
328
+ if (rawOrderId && typeof rawOrderId === "object") {
329
+ const nested = rawOrderId.order_id;
330
+ if (typeof nested === "string")
331
+ return nested;
332
+ if (typeof nested === "number" || typeof nested === "bigint")
333
+ return String(nested);
334
+ }
335
+ }
336
+ }
337
+ catch (err) {
338
+ log.debug("Unable to extract TWAP order id from transaction", { txHash, err });
339
+ }
340
+ return null;
341
+ }
126
342
  }
127
343
  function decimalToChainUnits(amount, decimals) {
128
344
  return BigInt(Math.round(amount * 10 ** decimals));
@@ -15,6 +15,7 @@ export interface DecibelPriceResponse {
15
15
  mark_px: number | string;
16
16
  mid_px: number | string;
17
17
  funding_rate_bps: number | string;
18
+ is_funding_positive?: boolean;
18
19
  open_interest: number | string;
19
20
  transaction_unix_ms?: number | string;
20
21
  }
@@ -37,11 +38,14 @@ export interface DecibelAccountOverviewResponse {
37
38
  realized_pnl?: number | string;
38
39
  margin_utilization?: number | string;
39
40
  cross_margin_ratio?: number | string;
41
+ usdc_cross_withdrawable_balance?: number | string;
42
+ usdc_isolated_withdrawable_balance?: number | string;
40
43
  }
41
44
  export interface DecibelAccountPositionResponse {
42
45
  market_name?: string;
43
46
  market?: string;
44
47
  size: number | string;
48
+ is_isolated?: boolean;
45
49
  entry_price: number | string;
46
50
  mark_price?: number | string;
47
51
  oracle_price?: number | string;
@@ -123,6 +127,21 @@ export interface DecibelFundingHistoryResponse {
123
127
  fee_amount: number | string;
124
128
  transaction_unix_ms: number | string;
125
129
  }
130
+ export interface DecibelTwapResponse {
131
+ market: string;
132
+ is_buy: boolean;
133
+ order_id: string;
134
+ client_order_id: string;
135
+ is_reduce_only: boolean;
136
+ start_unix_ms: number | string;
137
+ frequency_s: number | string;
138
+ duration_s: number | string;
139
+ orig_size: number | string;
140
+ remaining_size: number | string;
141
+ status: string;
142
+ transaction_unix_ms: number | string;
143
+ transaction_version: number | string;
144
+ }
126
145
  export interface DecibelFundHistoryResponse {
127
146
  type: string;
128
147
  amount: number | string;
@@ -168,6 +187,8 @@ export declare class DecibelRestClient {
168
187
  getCandlesticks(marketAddr: string, interval: string, startTime: number, endTime: number): Promise<DecibelCandlestickResponse[]>;
169
188
  getSubaccounts(ownerAddr: string): Promise<DecibelSubaccountResponse[]>;
170
189
  getFundingHistory(accountAddr: string, limit?: number, offset?: number): Promise<DecibelFundingHistoryResponse[]>;
190
+ getActiveTwaps(accountAddr: string, limit?: number): Promise<DecibelTwapResponse[]>;
191
+ getTwapHistory(accountAddr: string, limit?: number, offset?: number): Promise<DecibelTwapResponse[]>;
171
192
  getOrderById(accountAddr: string, marketAddr: string, orderId?: string, clientOrderId?: string): Promise<DecibelOrderLookupResponse>;
172
193
  getPublicTrades(marketAddr: string, limit?: number, offset?: number, orderId?: string): Promise<DecibelPublicTradeResponse[]>;
173
194
  getFundHistory(accountAddr: string, limit?: number, offset?: number): Promise<DecibelFundHistoryResponse[]>;
@@ -32,16 +32,16 @@ export class DecibelRestClient {
32
32
  return this.unwrapRows(payload);
33
33
  }
34
34
  async getOpenOrders(accountAddr) {
35
- const payload = await this.fetchApi("api/v1/open_orders", { user: accountAddr }, true);
35
+ const payload = await this.fetchApi("api/v1/open_orders", { account: accountAddr }, true);
36
36
  return this.unwrapRows(payload);
37
37
  }
38
38
  async getOrderHistory(accountAddr, limit = 200, offset = 0) {
39
- const payload = await this.fetchApi("api/v1/order_history", { user: accountAddr, limit, offset }, true);
39
+ const payload = await this.fetchApi("api/v1/order_history", { account: accountAddr, limit, offset }, true);
40
40
  return this.unwrapRows(payload);
41
41
  }
42
42
  async getTradeHistory(accountAddr, marketAddr, limit = 200, offset = 0) {
43
43
  const params = {
44
- user: accountAddr,
44
+ account: accountAddr,
45
45
  limit,
46
46
  offset,
47
47
  };
@@ -74,6 +74,14 @@ export class DecibelRestClient {
74
74
  const payload = await this.fetchApi("api/v1/funding_rate_history", { account: accountAddr, limit, offset }, true);
75
75
  return this.unwrapRows(payload);
76
76
  }
77
+ async getActiveTwaps(accountAddr, limit = 200) {
78
+ const payload = await this.fetchApi("api/v1/active_twaps", { account: accountAddr, limit }, true);
79
+ return this.unwrapRows(payload);
80
+ }
81
+ async getTwapHistory(accountAddr, limit = 200, offset = 0) {
82
+ const payload = await this.fetchApi("api/v1/twap_history", { account: accountAddr, limit, offset }, true);
83
+ return this.unwrapRows(payload);
84
+ }
77
85
  async getOrderById(accountAddr, marketAddr, orderId, clientOrderId) {
78
86
  const params = {
79
87
  account: accountAddr,
@@ -93,11 +101,11 @@ export class DecibelRestClient {
93
101
  };
94
102
  if (orderId)
95
103
  params.order_id = orderId;
96
- const payload = await this.fetchApi("api/v1/trades", params);
104
+ const payload = await this.fetchApi("api/v1/trades", params, true);
97
105
  return this.unwrapRows(payload);
98
106
  }
99
107
  async getFundHistory(accountAddr, limit = 200, offset = 0) {
100
- const payload = await this.fetchApi("api/v1/fund_history", { account: accountAddr, limit, offset }, true);
108
+ const payload = await this.fetchApi("api/v1/account_fund_history", { account: accountAddr, limit, offset }, true);
101
109
  return this.unwrapRows(payload);
102
110
  }
103
111
  async fetchApi(path, params, requiresAuth = false) {
@@ -116,9 +124,6 @@ export class DecibelRestClient {
116
124
  }
117
125
  headers.Authorization = `Bearer ${this.bearerToken}`;
118
126
  }
119
- else if (this.bearerToken) {
120
- headers.Authorization = `Bearer ${this.bearerToken}`;
121
- }
122
127
  log.debug(`GET ${url.pathname}${url.search}`);
123
128
  const json = await fetchWithTimeout(url.toString(), {
124
129
  headers,
@@ -138,8 +143,10 @@ export class DecibelRestClient {
138
143
  }
139
144
  if (isRecord(payload)) {
140
145
  const candidates = [
146
+ payload.items,
141
147
  payload.results,
142
148
  payload.data,
149
+ isRecord(payload.data) ? payload.data.items : undefined,
143
150
  isRecord(payload.data) ? payload.data.rows : undefined,
144
151
  isRecord(payload.data) ? payload.data.orders : undefined,
145
152
  isRecord(payload.data) ? payload.data.trades : undefined,