@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 +19 -1
- package/README.md +12 -2
- 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/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/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 +1 -1
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
|
@@ -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
|
|
137
|
-
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)
|
|
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
|
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[]>;
|
|
@@ -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", {
|
|
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", {
|
|
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
|
-
|
|
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/
|
|
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,
|