@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.
- package/CHANGELOG.md +19 -1
- package/README.md +42 -4
- package/dist/adapters/aevo.d.ts +1 -6
- package/dist/adapters/aevo.js +0 -21
- package/dist/adapters/certification.js +55 -9
- package/dist/adapters/decibel/order-manager.d.ts +41 -0
- package/dist/adapters/decibel/order-manager.js +216 -0
- package/dist/adapters/decibel/rest-client.d.ts +21 -0
- package/dist/adapters/decibel/rest-client.js +15 -8
- package/dist/adapters/decibel/ws-feed.js +19 -4
- package/dist/adapters/decibel.d.ts +15 -10
- package/dist/adapters/decibel.js +371 -50
- package/dist/adapters/hyperliquid.d.ts +1 -6
- package/dist/adapters/hyperliquid.js +0 -18
- package/dist/adapters/interface.d.ts +15 -14
- package/dist/adapters/orderly.d.ts +1 -9
- package/dist/adapters/orderly.js +0 -33
- package/dist/adapters/paradex.d.ts +1 -9
- package/dist/adapters/paradex.js +1 -34
- package/dist/cli/experience.js +0 -1
- package/dist/cli/program.js +28 -5
- package/dist/commands/arb/alert.d.ts +0 -4
- package/dist/commands/arb/alert.js +4 -14
- package/dist/commands/markets/index.js +2 -0
- package/dist/commands/markets/read-simple.d.ts +2 -0
- package/dist/commands/markets/read-simple.js +130 -0
- package/dist/commands/order/advanced-simple.d.ts +2 -0
- package/dist/commands/order/advanced-simple.js +820 -0
- package/dist/commands/order/index.js +2 -0
- package/dist/index.js +35 -1
- package/dist/lib/exit-codes.js +5 -1
- package/dist/lib/prompts.d.ts +0 -18
- package/dist/lib/prompts.js +1 -22
- package/dist/lib/schema.d.ts +8 -8
- package/dist/lib/schema.js +47 -8
- 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.
|
|
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
|
|
3
|
+
Universal CLI for perpetual DEXes.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/raintree-technology/perps/actions/workflows/ci.yml)
|
|
6
6
|
[](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
|
|
121
|
-
perps order --help #
|
|
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)
|
|
210
|
+
[Raintree Technology](https://raintree.technology)
|
|
173
211
|
|
|
174
212
|
## License
|
|
175
213
|
|
package/dist/adapters/aevo.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/adapters/aevo.js
CHANGED
|
@@ -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: [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
44
|
-
|
|
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:
|
|
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[]>;
|