@raintree-technology/perps 0.1.2 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +42 -4
  3. package/dist/adapters/aevo.d.ts +1 -6
  4. package/dist/adapters/aevo.js +0 -21
  5. package/dist/adapters/certification.js +55 -9
  6. package/dist/adapters/decibel/order-manager.d.ts +41 -0
  7. package/dist/adapters/decibel/order-manager.js +216 -0
  8. package/dist/adapters/decibel/rest-client.d.ts +21 -0
  9. package/dist/adapters/decibel/rest-client.js +15 -8
  10. package/dist/adapters/decibel/ws-feed.js +19 -4
  11. package/dist/adapters/decibel.d.ts +15 -10
  12. package/dist/adapters/decibel.js +371 -50
  13. package/dist/adapters/hyperliquid.d.ts +1 -6
  14. package/dist/adapters/hyperliquid.js +0 -18
  15. package/dist/adapters/interface.d.ts +15 -14
  16. package/dist/adapters/orderly.d.ts +1 -9
  17. package/dist/adapters/orderly.js +0 -33
  18. package/dist/adapters/paradex.d.ts +1 -9
  19. package/dist/adapters/paradex.js +1 -34
  20. package/dist/cli/experience.js +0 -1
  21. package/dist/cli/program.js +28 -5
  22. package/dist/commands/arb/alert.d.ts +0 -4
  23. package/dist/commands/arb/alert.js +4 -14
  24. package/dist/commands/markets/index.js +2 -0
  25. package/dist/commands/markets/read-simple.d.ts +2 -0
  26. package/dist/commands/markets/read-simple.js +130 -0
  27. package/dist/commands/order/advanced-simple.d.ts +2 -0
  28. package/dist/commands/order/advanced-simple.js +820 -0
  29. package/dist/commands/order/index.js +2 -0
  30. package/dist/index.js +35 -1
  31. package/dist/lib/exit-codes.js +5 -1
  32. package/dist/lib/prompts.d.ts +0 -18
  33. package/dist/lib/prompts.js +1 -22
  34. package/dist/lib/schema.d.ts +8 -8
  35. package/dist/lib/schema.js +47 -8
  36. package/package.json +5 -2
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
@@ -1,6 +1,6 @@
1
1
  # perps
2
2
 
3
- Universal CLI for perpetual DEXes — by [Raintree Technology](https://raintree.technology).
3
+ Universal CLI for perpetual DEXes.
4
4
 
5
5
  [![CI](https://github.com/raintree-technology/perps/actions/workflows/ci.yml/badge.svg)](https://github.com/raintree-technology/perps/actions/workflows/ci.yml)
6
6
  [![npm](https://img.shields.io/npm/v/@raintree-technology/perps)](https://www.npmjs.com/package/@raintree-technology/perps)
@@ -12,6 +12,22 @@ npm install -g @raintree-technology/perps
12
12
  perps --help
13
13
  ```
14
14
 
15
+ ## Install Paths
16
+
17
+ Canonical install path:
18
+
19
+ ```bash
20
+ npm install -g @raintree-technology/perps
21
+ ```
22
+
23
+ GitHub install path (for source snapshots and pre-release testing):
24
+
25
+ ```bash
26
+ npm install github:raintree-technology/perps
27
+ ```
28
+
29
+ The package includes lifecycle build hooks so git installs produce runnable CLI artifacts (`dist/`) automatically.
30
+
15
31
  ## Supported Exchanges
16
32
 
17
33
  | Exchange | Chain | Status | Onboarding |
@@ -117,8 +133,8 @@ See **[CAPABILITIES.md](CAPABILITIES.md)** for the full reference — every comm
117
133
  ## Command Surface
118
134
 
119
135
  ```bash
120
- perps markets ls --help # List markets with funding, OI, volume
121
- 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)
122
138
  perps arb --help # Cross-exchange arbitrage
123
139
  perps account --help # Portfolio (positions, balances)
124
140
  perps agent --help # HTTP agent gateway
@@ -136,6 +152,28 @@ perps doctor # Health check
136
152
 
137
153
  Every command supports `--json` for machine-readable output and `--help` for usage details.
138
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
+
165
+ Agent-friendly mode:
166
+
167
+ ```bash
168
+ # Non-interactive execution
169
+ perps setup wizard --yes --json
170
+
171
+ # Machine-readable responses with schema version
172
+ perps markets ls --json
173
+ ```
174
+
175
+ JSON payloads are schema-versioned, and failures return deterministic exit codes.
176
+
139
177
  ## Exit Codes
140
178
 
141
179
  | Code | Meaning |
@@ -169,7 +207,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Please read our [Code of
169
207
 
170
208
  ## Author
171
209
 
172
- [Raintree Technology](https://raintree.technology) ([@raintree_tech](https://x.com/raintree_tech)) — created by [Zachary Roth](https://zacharyr0th.com) ([@zacharyr0th](https://x.com/zacharyr0th))
210
+ [Raintree Technology](https://raintree.technology)
173
211
 
174
212
  ## License
175
213
 
@@ -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[]>;