@raintree-technology/perps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/adapters/aevo.d.ts +64 -0
- package/dist/adapters/aevo.js +899 -0
- package/dist/adapters/certification.d.ts +33 -0
- package/dist/adapters/certification.js +99 -0
- package/dist/adapters/decibel/order-manager.d.ts +45 -0
- package/dist/adapters/decibel/order-manager.js +140 -0
- package/dist/adapters/decibel/rest-client.d.ts +176 -0
- package/dist/adapters/decibel/rest-client.js +155 -0
- package/dist/adapters/decibel/ws-feed.d.ts +28 -0
- package/dist/adapters/decibel/ws-feed.js +166 -0
- package/dist/adapters/decibel.d.ts +108 -0
- package/dist/adapters/decibel.js +1377 -0
- package/dist/adapters/hyperliquid.d.ts +63 -0
- package/dist/adapters/hyperliquid.js +797 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/interface.d.ts +310 -0
- package/dist/adapters/interface.js +15 -0
- package/dist/adapters/orderly.d.ts +70 -0
- package/dist/adapters/orderly.js +936 -0
- package/dist/adapters/paradex.d.ts +69 -0
- package/dist/adapters/paradex.js +862 -0
- package/dist/adapters/utils.d.ts +17 -0
- package/dist/adapters/utils.js +122 -0
- package/dist/cli/command-metadata.d.ts +2 -0
- package/dist/cli/command-metadata.js +44 -0
- package/dist/cli/context.d.ts +14 -0
- package/dist/cli/context.js +59 -0
- package/dist/cli/experience.d.ts +48 -0
- package/dist/cli/experience.js +243 -0
- package/dist/cli/ink/app/AppShell.d.ts +12 -0
- package/dist/cli/ink/app/AppShell.js +32 -0
- package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
- package/dist/cli/ink/app/MetricStrip.js +14 -0
- package/dist/cli/ink/app/Panel.d.ts +9 -0
- package/dist/cli/ink/app/Panel.js +7 -0
- package/dist/cli/ink/app/ascii.d.ts +2 -0
- package/dist/cli/ink/app/ascii.js +46 -0
- package/dist/cli/ink/app/index.d.ts +5 -0
- package/dist/cli/ink/app/index.js +4 -0
- package/dist/cli/ink/app/types.d.ts +15 -0
- package/dist/cli/ink/app/types.js +1 -0
- package/dist/cli/ink/components/PnL.d.ts +12 -0
- package/dist/cli/ink/components/PnL.js +23 -0
- package/dist/cli/ink/components/Spinner.d.ts +13 -0
- package/dist/cli/ink/components/Spinner.js +13 -0
- package/dist/cli/ink/components/Table.d.ts +14 -0
- package/dist/cli/ink/components/Table.js +42 -0
- package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
- package/dist/cli/ink/components/WatchHeader.js +18 -0
- package/dist/cli/ink/components/index.d.ts +4 -0
- package/dist/cli/ink/components/index.js +4 -0
- package/dist/cli/ink/index.d.ts +4 -0
- package/dist/cli/ink/index.js +4 -0
- package/dist/cli/ink/render.d.ts +12 -0
- package/dist/cli/ink/render.js +21 -0
- package/dist/cli/ink/theme.d.ts +29 -0
- package/dist/cli/ink/theme.js +40 -0
- package/dist/cli/network-defaults.d.ts +10 -0
- package/dist/cli/network-defaults.js +35 -0
- package/dist/cli/output.d.ts +11 -0
- package/dist/cli/output.js +115 -0
- package/dist/cli/program.d.ts +18 -0
- package/dist/cli/program.js +164 -0
- package/dist/cli/watch.d.ts +19 -0
- package/dist/cli/watch.js +35 -0
- package/dist/client/index.d.ts +55 -0
- package/dist/client/index.js +157 -0
- package/dist/commands/account/add.d.ts +2 -0
- package/dist/commands/account/add.js +510 -0
- package/dist/commands/account/balances-simple.d.ts +5 -0
- package/dist/commands/account/balances-simple.js +63 -0
- package/dist/commands/account/index.d.ts +2 -0
- package/dist/commands/account/index.js +17 -0
- package/dist/commands/account/ls.d.ts +2 -0
- package/dist/commands/account/ls.js +95 -0
- package/dist/commands/account/positions-simple.d.ts +5 -0
- package/dist/commands/account/positions-simple.js +77 -0
- package/dist/commands/account/remove.d.ts +2 -0
- package/dist/commands/account/remove.js +47 -0
- package/dist/commands/account/set-default.d.ts +2 -0
- package/dist/commands/account/set-default.js +47 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +126 -0
- package/dist/commands/arb/alert.d.ts +6 -0
- package/dist/commands/arb/alert.js +88 -0
- package/dist/commands/arb/basis-execute.d.ts +6 -0
- package/dist/commands/arb/basis-execute.js +332 -0
- package/dist/commands/arb/basis.d.ts +6 -0
- package/dist/commands/arb/basis.js +181 -0
- package/dist/commands/arb/compare.d.ts +6 -0
- package/dist/commands/arb/compare.js +216 -0
- package/dist/commands/arb/execute.d.ts +6 -0
- package/dist/commands/arb/execute.js +467 -0
- package/dist/commands/arb/funding.d.ts +6 -0
- package/dist/commands/arb/funding.js +201 -0
- package/dist/commands/arb/history.d.ts +6 -0
- package/dist/commands/arb/history.js +153 -0
- package/dist/commands/arb/index.d.ts +6 -0
- package/dist/commands/arb/index.js +29 -0
- package/dist/commands/arb/positions.d.ts +6 -0
- package/dist/commands/arb/positions.js +158 -0
- package/dist/commands/arb/spread.d.ts +6 -0
- package/dist/commands/arb/spread.js +253 -0
- package/dist/commands/arb/track.d.ts +6 -0
- package/dist/commands/arb/track.js +259 -0
- package/dist/commands/asset/book-simple.d.ts +5 -0
- package/dist/commands/asset/book-simple.js +77 -0
- package/dist/commands/asset/index.d.ts +2 -0
- package/dist/commands/asset/index.js +5 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +161 -0
- package/dist/commands/config/index.d.ts +5 -0
- package/dist/commands/config/index.js +109 -0
- package/dist/commands/data/index.d.ts +31 -0
- package/dist/commands/data/index.js +1466 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +201 -0
- package/dist/commands/exchange/index.d.ts +2 -0
- package/dist/commands/exchange/index.js +107 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +48 -0
- package/dist/commands/markets/index.d.ts +2 -0
- package/dist/commands/markets/index.js +5 -0
- package/dist/commands/markets/ls-simple.d.ts +7 -0
- package/dist/commands/markets/ls-simple.js +277 -0
- package/dist/commands/operator/index.d.ts +2 -0
- package/dist/commands/operator/index.js +146 -0
- package/dist/commands/order/cancel-simple.d.ts +5 -0
- package/dist/commands/order/cancel-simple.js +104 -0
- package/dist/commands/order/index.d.ts +2 -0
- package/dist/commands/order/index.js +13 -0
- package/dist/commands/order/limit-simple.d.ts +5 -0
- package/dist/commands/order/limit-simple.js +195 -0
- package/dist/commands/order/market-simple.d.ts +5 -0
- package/dist/commands/order/market-simple.js +190 -0
- package/dist/commands/order/shared.d.ts +17 -0
- package/dist/commands/order/shared.js +51 -0
- package/dist/commands/order/trigger-simple.d.ts +5 -0
- package/dist/commands/order/trigger-simple.js +246 -0
- package/dist/commands/referral/index.d.ts +2 -0
- package/dist/commands/referral/index.js +7 -0
- package/dist/commands/referral/set.d.ts +2 -0
- package/dist/commands/referral/set.js +26 -0
- package/dist/commands/referral/status.d.ts +2 -0
- package/dist/commands/referral/status.js +31 -0
- package/dist/commands/replay/index.d.ts +2 -0
- package/dist/commands/replay/index.js +152 -0
- package/dist/commands/risk/analytics.d.ts +2 -0
- package/dist/commands/risk/analytics.js +64 -0
- package/dist/commands/risk/audit.d.ts +2 -0
- package/dist/commands/risk/audit.js +52 -0
- package/dist/commands/risk/index.d.ts +2 -0
- package/dist/commands/risk/index.js +9 -0
- package/dist/commands/risk/rules.d.ts +2 -0
- package/dist/commands/risk/rules.js +102 -0
- package/dist/commands/server.d.ts +2 -0
- package/dist/commands/server.js +208 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +478 -0
- package/dist/commands/signal/index.d.ts +2 -0
- package/dist/commands/signal/index.js +129 -0
- package/dist/commands/state/index.d.ts +2 -0
- package/dist/commands/state/index.js +5 -0
- package/dist/commands/state/show.d.ts +2 -0
- package/dist/commands/state/show.js +105 -0
- package/dist/commands/strategy/index.d.ts +4 -0
- package/dist/commands/strategy/index.js +73 -0
- package/dist/commands/traces/index.d.ts +2 -0
- package/dist/commands/traces/index.js +76 -0
- package/dist/commands/ui/demo.d.ts +9 -0
- package/dist/commands/ui/demo.js +195 -0
- package/dist/commands/ui/index.d.ts +2 -0
- package/dist/commands/ui/index.js +7 -0
- package/dist/commands/ui/terminal.d.ts +2 -0
- package/dist/commands/ui/terminal.js +255 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/agent/audit.d.ts +12 -0
- package/dist/lib/agent/audit.js +13 -0
- package/dist/lib/agent/gateway.d.ts +13 -0
- package/dist/lib/agent/gateway.js +598 -0
- package/dist/lib/agent/metrics.d.ts +33 -0
- package/dist/lib/agent/metrics.js +175 -0
- package/dist/lib/agent/signature.d.ts +8 -0
- package/dist/lib/agent/signature.js +28 -0
- package/dist/lib/agent/tools.d.ts +28 -0
- package/dist/lib/agent/tools.js +453 -0
- package/dist/lib/agent/x402.d.ts +23 -0
- package/dist/lib/agent/x402.js +62 -0
- package/dist/lib/api-wallet.d.ts +69 -0
- package/dist/lib/api-wallet.js +101 -0
- package/dist/lib/balance-watcher.d.ts +25 -0
- package/dist/lib/balance-watcher.js +83 -0
- package/dist/lib/book-watcher.d.ts +25 -0
- package/dist/lib/book-watcher.js +48 -0
- package/dist/lib/config.d.ts +88 -0
- package/dist/lib/config.js +427 -0
- package/dist/lib/constants.d.ts +50 -0
- package/dist/lib/constants.js +84 -0
- package/dist/lib/contracts.d.ts +7 -0
- package/dist/lib/contracts.js +8 -0
- package/dist/lib/credential-vault.d.ts +22 -0
- package/dist/lib/credential-vault.js +109 -0
- package/dist/lib/db/accounts.d.ts +83 -0
- package/dist/lib/db/accounts.js +203 -0
- package/dist/lib/db/funding-history.d.ts +69 -0
- package/dist/lib/db/funding-history.js +183 -0
- package/dist/lib/db/index.d.ts +11 -0
- package/dist/lib/db/index.js +272 -0
- package/dist/lib/events/bus.d.ts +10 -0
- package/dist/lib/events/bus.js +17 -0
- package/dist/lib/events/types.d.ts +51 -0
- package/dist/lib/events/types.js +1 -0
- package/dist/lib/exchange.d.ts +30 -0
- package/dist/lib/exchange.js +84 -0
- package/dist/lib/execution/journal.d.ts +25 -0
- package/dist/lib/execution/journal.js +158 -0
- package/dist/lib/execution/safety.d.ts +34 -0
- package/dist/lib/execution/safety.js +197 -0
- package/dist/lib/exit-codes.d.ts +18 -0
- package/dist/lib/exit-codes.js +60 -0
- package/dist/lib/fetch.d.ts +18 -0
- package/dist/lib/fetch.js +66 -0
- package/dist/lib/fs-security.d.ts +10 -0
- package/dist/lib/fs-security.js +26 -0
- package/dist/lib/funding-tracker.d.ts +40 -0
- package/dist/lib/funding-tracker.js +118 -0
- package/dist/lib/logger.d.ts +27 -0
- package/dist/lib/logger.js +82 -0
- package/dist/lib/network-model.d.ts +13 -0
- package/dist/lib/network-model.js +30 -0
- package/dist/lib/onboarding.d.ts +133 -0
- package/dist/lib/onboarding.js +1459 -0
- package/dist/lib/operator-state.d.ts +25 -0
- package/dist/lib/operator-state.js +82 -0
- package/dist/lib/orders-watcher.d.ts +24 -0
- package/dist/lib/orders-watcher.js +74 -0
- package/dist/lib/paths.d.ts +20 -0
- package/dist/lib/paths.js +23 -0
- package/dist/lib/portfolio-watcher.d.ts +33 -0
- package/dist/lib/portfolio-watcher.js +95 -0
- package/dist/lib/position-watcher.d.ts +16 -0
- package/dist/lib/position-watcher.js +44 -0
- package/dist/lib/price-watcher.d.ts +15 -0
- package/dist/lib/price-watcher.js +84 -0
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +105 -0
- package/dist/lib/rate-limit.d.ts +32 -0
- package/dist/lib/rate-limit.js +88 -0
- package/dist/lib/risk/analytics.d.ts +39 -0
- package/dist/lib/risk/analytics.js +98 -0
- package/dist/lib/risk/drawdown.d.ts +18 -0
- package/dist/lib/risk/drawdown.js +49 -0
- package/dist/lib/risk/evaluation-log.d.ts +29 -0
- package/dist/lib/risk/evaluation-log.js +61 -0
- package/dist/lib/risk/index.d.ts +4 -0
- package/dist/lib/risk/index.js +4 -0
- package/dist/lib/risk/limits.d.ts +23 -0
- package/dist/lib/risk/limits.js +27 -0
- package/dist/lib/risk/manager.d.ts +32 -0
- package/dist/lib/risk/manager.js +85 -0
- package/dist/lib/risk/policy-middleware.d.ts +33 -0
- package/dist/lib/risk/policy-middleware.js +267 -0
- package/dist/lib/risk/position-sizer.d.ts +9 -0
- package/dist/lib/risk/position-sizer.js +14 -0
- package/dist/lib/risk/rules-store.d.ts +16 -0
- package/dist/lib/risk/rules-store.js +47 -0
- package/dist/lib/schema.d.ts +254 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/secrets.d.ts +3 -0
- package/dist/lib/secrets.js +62 -0
- package/dist/lib/settings.d.ts +24 -0
- package/dist/lib/settings.js +86 -0
- package/dist/lib/signals.d.ts +73 -0
- package/dist/lib/signals.js +136 -0
- package/dist/lib/stable-stringify.d.ts +6 -0
- package/dist/lib/stable-stringify.js +17 -0
- package/dist/lib/state-context.d.ts +44 -0
- package/dist/lib/state-context.js +133 -0
- package/dist/lib/strategy/basis-trade.d.ts +2 -0
- package/dist/lib/strategy/basis-trade.js +24 -0
- package/dist/lib/strategy/funding-arb.d.ts +2 -0
- package/dist/lib/strategy/funding-arb.js +23 -0
- package/dist/lib/strategy/interface.d.ts +23 -0
- package/dist/lib/strategy/interface.js +1 -0
- package/dist/lib/strategy/registry.d.ts +4 -0
- package/dist/lib/strategy/registry.js +10 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +101 -0
- package/dist/lib/trace-queries.d.ts +20 -0
- package/dist/lib/trace-queries.js +133 -0
- package/dist/lib/trace.d.ts +1 -0
- package/dist/lib/trace.js +4 -0
- package/dist/lib/trade-reputation.d.ts +6 -0
- package/dist/lib/trade-reputation.js +99 -0
- package/dist/lib/ui-tokens.d.ts +21 -0
- package/dist/lib/ui-tokens.js +26 -0
- package/dist/lib/validate.d.ts +39 -0
- package/dist/lib/validate.js +108 -0
- package/dist/lib/validation.d.ts +9 -0
- package/dist/lib/validation.js +64 -0
- package/dist/server/cache.d.ts +38 -0
- package/dist/server/cache.js +56 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +89 -0
- package/dist/server/ipc.d.ts +18 -0
- package/dist/server/ipc.js +159 -0
- package/dist/server/subscriptions.d.ts +18 -0
- package/dist/server/subscriptions.js +114 -0
- package/package.json +124 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orderly Network Adapter
|
|
3
|
+
* Implements PerpDEXAdapter interface for Orderly Network
|
|
4
|
+
*
|
|
5
|
+
* API Docs: https://orderly.network/docs
|
|
6
|
+
* Base URL: https://api-evm.orderly.org/v1
|
|
7
|
+
*/
|
|
8
|
+
import { KeyPair } from "near-api-js";
|
|
9
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
10
|
+
import { keccak_256 } from "@noble/hashes/sha3";
|
|
11
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
12
|
+
import { registerAdapter, } from "./interface.js";
|
|
13
|
+
import { fetchWithTimeout } from "../lib/fetch.js";
|
|
14
|
+
import { createLogger } from "../lib/logger.js";
|
|
15
|
+
import { MAJOR_MARKETS } from "../lib/constants.js";
|
|
16
|
+
import { isRecord, pickArray, pickObject, pickUnknown, pickString, pickNumber, pickBoolean, toTimestampMs } from "./utils.js";
|
|
17
|
+
const log = createLogger("orderly");
|
|
18
|
+
export class OrderlyAdapter {
|
|
19
|
+
info = {
|
|
20
|
+
id: "orderly",
|
|
21
|
+
name: "Orderly Network",
|
|
22
|
+
type: "dex",
|
|
23
|
+
chains: ["arbitrum", "optimism", "polygon", "base"],
|
|
24
|
+
features: {
|
|
25
|
+
spot: true,
|
|
26
|
+
perp: true,
|
|
27
|
+
margin: true,
|
|
28
|
+
crossMargin: true,
|
|
29
|
+
isolatedMargin: false,
|
|
30
|
+
stopOrders: true,
|
|
31
|
+
takeProfitOrders: true,
|
|
32
|
+
postOnly: true,
|
|
33
|
+
reduceOnly: true,
|
|
34
|
+
subaccounts: false,
|
|
35
|
+
modifyOrders: false,
|
|
36
|
+
batchOrders: true,
|
|
37
|
+
cancelAllAfter: true,
|
|
38
|
+
publicTrades: true,
|
|
39
|
+
fundingHistory: true,
|
|
40
|
+
orderHistory: true,
|
|
41
|
+
mmp: false,
|
|
42
|
+
twapOrders: false,
|
|
43
|
+
},
|
|
44
|
+
urls: {
|
|
45
|
+
app: "https://app.orderly.network",
|
|
46
|
+
api: "https://api-evm.orderly.org/v1",
|
|
47
|
+
docs: "https://orderly.network/docs",
|
|
48
|
+
testnet: "https://testnet-api.orderly.org/v1",
|
|
49
|
+
},
|
|
50
|
+
implementation: {
|
|
51
|
+
marketData: "full",
|
|
52
|
+
authenticatedReads: "full",
|
|
53
|
+
orderLifecycle: "full",
|
|
54
|
+
orderCancellation: "full",
|
|
55
|
+
subscriptions: "full",
|
|
56
|
+
advancedTrading: "full",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
baseUrl = "https://api-evm.orderly.org/v1";
|
|
60
|
+
config = null;
|
|
61
|
+
connected = false;
|
|
62
|
+
marketsCache = null;
|
|
63
|
+
subscriptionTimers = new Set();
|
|
64
|
+
async connect(config) {
|
|
65
|
+
this.config = config;
|
|
66
|
+
const configuredBase = config.credentials?.restUrl;
|
|
67
|
+
const network = config.credentials?.network;
|
|
68
|
+
if (configuredBase) {
|
|
69
|
+
this.baseUrl = configuredBase.replace(/\/$/, "");
|
|
70
|
+
}
|
|
71
|
+
else if (network === "testnet" || (network !== "mainnet" && config.testnet)) {
|
|
72
|
+
this.baseUrl = "https://testnet-api.orderly.org/v1";
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
this.baseUrl = "https://api-evm.orderly.org/v1";
|
|
76
|
+
}
|
|
77
|
+
await this.fetchMarkets();
|
|
78
|
+
this.connected = true;
|
|
79
|
+
}
|
|
80
|
+
async disconnect() {
|
|
81
|
+
for (const timer of this.subscriptionTimers) {
|
|
82
|
+
clearInterval(timer);
|
|
83
|
+
}
|
|
84
|
+
this.subscriptionTimers.clear();
|
|
85
|
+
this.config = null;
|
|
86
|
+
this.marketsCache = null;
|
|
87
|
+
this.connected = false;
|
|
88
|
+
}
|
|
89
|
+
isConnected() {
|
|
90
|
+
return this.connected;
|
|
91
|
+
}
|
|
92
|
+
// --------------------------------------------------------------------------
|
|
93
|
+
// Private API helpers
|
|
94
|
+
// --------------------------------------------------------------------------
|
|
95
|
+
async fetchPublic(endpoint, options) {
|
|
96
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
97
|
+
log.debug(`Fetching ${endpoint}`);
|
|
98
|
+
return fetchWithTimeout(url, options);
|
|
99
|
+
}
|
|
100
|
+
async fetchPrivate(args) {
|
|
101
|
+
const auth = this.getAuthConfig(!!args.signedPayload);
|
|
102
|
+
let payload = args.body;
|
|
103
|
+
const query = { ...(args.query ?? {}) };
|
|
104
|
+
if (args.signedPayload) {
|
|
105
|
+
if (!auth.tradingSecret) {
|
|
106
|
+
throw new Error("Orderly write operations require ORDERLY_TRADING_SECRET");
|
|
107
|
+
}
|
|
108
|
+
const withSignature = {
|
|
109
|
+
...args.signedPayload,
|
|
110
|
+
};
|
|
111
|
+
withSignature.signature = this.signTradingPayload(withSignature, auth.tradingSecret);
|
|
112
|
+
if (args.method === "DELETE") {
|
|
113
|
+
for (const [key, value] of Object.entries(withSignature)) {
|
|
114
|
+
query[key] = stringifyValue(value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
payload = withSignature;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const queryString = this.buildQueryString(query);
|
|
122
|
+
const endpointPath = `${args.endpoint}${queryString}`;
|
|
123
|
+
const signingPath = `/v1${endpointPath}`;
|
|
124
|
+
const includeQuery = args.method === "GET" || (args.method === "DELETE" && !payload);
|
|
125
|
+
const signaturePayload = includeQuery ? "" : payload ? JSON.stringify(payload) : "";
|
|
126
|
+
const headers = this.buildSignedHeaders({
|
|
127
|
+
method: args.method,
|
|
128
|
+
signingPath,
|
|
129
|
+
signaturePayload,
|
|
130
|
+
auth,
|
|
131
|
+
includeTradingKey: !!args.signedPayload,
|
|
132
|
+
});
|
|
133
|
+
const url = `${this.baseUrl}${endpointPath}`;
|
|
134
|
+
const response = await fetchWithTimeout(url, {
|
|
135
|
+
method: args.method,
|
|
136
|
+
headers,
|
|
137
|
+
body: payload ? JSON.stringify(payload) : undefined,
|
|
138
|
+
});
|
|
139
|
+
if (isRecord(response) && response.success === false) {
|
|
140
|
+
const message = (typeof response.message === "string" && response.message) || "Orderly request failed";
|
|
141
|
+
throw new Error(message);
|
|
142
|
+
}
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
async fetchMarkets() {
|
|
146
|
+
if (this.marketsCache)
|
|
147
|
+
return this.marketsCache;
|
|
148
|
+
const response = await this.fetchPublic("/public/info");
|
|
149
|
+
if (!response.success) {
|
|
150
|
+
throw new Error("Failed to fetch Orderly markets");
|
|
151
|
+
}
|
|
152
|
+
this.marketsCache = response.data.rows.filter((m) => m.symbol.startsWith("PERP_"));
|
|
153
|
+
return this.marketsCache;
|
|
154
|
+
}
|
|
155
|
+
getAuthConfig(requireTrading) {
|
|
156
|
+
const credentials = this.config?.credentials;
|
|
157
|
+
const accountId = credentials?.orderlyAccountId ?? credentials?.accountAddress ?? credentials?.subaccountId;
|
|
158
|
+
const key = credentials?.orderlyKey ?? credentials?.apiKey;
|
|
159
|
+
const secret = credentials?.orderlySecret ?? credentials?.apiSecret;
|
|
160
|
+
const tradingSecret = credentials?.orderlyTradingSecret ??
|
|
161
|
+
credentials?.passphrase ??
|
|
162
|
+
(credentials?.privateKey ? credentials.privateKey : undefined);
|
|
163
|
+
const tradingKey = credentials?.orderlyTradingKey ??
|
|
164
|
+
credentials?.subaccountId ??
|
|
165
|
+
(tradingSecret ? this.deriveTradingPublicKey(tradingSecret) : undefined);
|
|
166
|
+
if (!accountId || !key || !secret) {
|
|
167
|
+
throw new Error("Orderly authenticated endpoints require ORDERLY_ACCOUNT_ID, ORDERLY_KEY, and ORDERLY_SECRET");
|
|
168
|
+
}
|
|
169
|
+
if (requireTrading && !tradingSecret) {
|
|
170
|
+
throw new Error("Orderly trading requires ORDERLY_TRADING_SECRET");
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
accountId,
|
|
174
|
+
key,
|
|
175
|
+
secret,
|
|
176
|
+
tradingSecret,
|
|
177
|
+
tradingKey,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
buildSignedHeaders(args) {
|
|
181
|
+
const timestamp = Date.now().toString();
|
|
182
|
+
const signingMessage = `${timestamp}${args.method.toUpperCase()}${args.signingPath}${args.signaturePayload}`;
|
|
183
|
+
const orderlyKeyPair = KeyPair.fromString(args.auth.secret);
|
|
184
|
+
const signature = Buffer.from(orderlyKeyPair.sign(Buffer.from(signingMessage)).signature).toString("base64");
|
|
185
|
+
const headers = {
|
|
186
|
+
Accept: "application/json",
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
"orderly-account-id": args.auth.accountId,
|
|
189
|
+
"orderly-key": args.auth.key,
|
|
190
|
+
"orderly-signature": signature,
|
|
191
|
+
"orderly-timestamp": timestamp,
|
|
192
|
+
};
|
|
193
|
+
if (args.includeTradingKey && args.auth.tradingKey) {
|
|
194
|
+
headers["orderly-trading-key"] = args.auth.tradingKey;
|
|
195
|
+
}
|
|
196
|
+
return headers;
|
|
197
|
+
}
|
|
198
|
+
signTradingPayload(payload, tradingSecret) {
|
|
199
|
+
const canonical = Object.keys(payload)
|
|
200
|
+
.sort()
|
|
201
|
+
.map((key) => `${key}=${stringifyValue(payload[key])}`)
|
|
202
|
+
.join("&");
|
|
203
|
+
const privateKeyHex = stripHexPrefix(tradingSecret);
|
|
204
|
+
const msgHash = keccak_256(new TextEncoder().encode(canonical));
|
|
205
|
+
const sig = secp256k1.sign(msgHash, privateKeyHex, { lowS: true });
|
|
206
|
+
const r = sig.r.toString(16).padStart(64, "0");
|
|
207
|
+
const s = sig.s.toString(16).padStart(64, "0");
|
|
208
|
+
const recovery = sig.recovery ?? 0;
|
|
209
|
+
return `${r}${s}0${recovery}`;
|
|
210
|
+
}
|
|
211
|
+
deriveTradingPublicKey(tradingSecret) {
|
|
212
|
+
const pub = secp256k1.getPublicKey(stripHexPrefix(tradingSecret), false);
|
|
213
|
+
return bytesToHex(pub).slice(2); // remove 04 prefix
|
|
214
|
+
}
|
|
215
|
+
buildQueryString(query) {
|
|
216
|
+
const params = new URLSearchParams();
|
|
217
|
+
for (const [key, value] of Object.entries(query)) {
|
|
218
|
+
if (value === undefined || value === null)
|
|
219
|
+
continue;
|
|
220
|
+
params.set(key, String(value));
|
|
221
|
+
}
|
|
222
|
+
const encoded = params.toString();
|
|
223
|
+
return encoded ? `?${encoded}` : "";
|
|
224
|
+
}
|
|
225
|
+
canUseReadAuth() {
|
|
226
|
+
const credentials = this.config?.credentials;
|
|
227
|
+
return !!(credentials &&
|
|
228
|
+
(credentials.orderlyAccountId ?? credentials.accountAddress) &&
|
|
229
|
+
(credentials.orderlyKey ?? credentials.apiKey) &&
|
|
230
|
+
(credentials.orderlySecret ?? credentials.apiSecret));
|
|
231
|
+
}
|
|
232
|
+
// --------------------------------------------------------------------------
|
|
233
|
+
// Public Market Data
|
|
234
|
+
// --------------------------------------------------------------------------
|
|
235
|
+
async getMarkets() {
|
|
236
|
+
const markets = await this.fetchMarkets();
|
|
237
|
+
return markets.map((m) => {
|
|
238
|
+
const [_, baseAsset = m.symbol, quoteAsset = "USDC"] = m.symbol.split("_");
|
|
239
|
+
const maxLeverage = Math.floor(1 / m.base_imr) || 20;
|
|
240
|
+
return {
|
|
241
|
+
symbol: this.toStandardSymbol(m.symbol),
|
|
242
|
+
baseAsset,
|
|
243
|
+
quoteAsset,
|
|
244
|
+
type: "perp",
|
|
245
|
+
maxLeverage,
|
|
246
|
+
minSize: m.base_tick.toString(),
|
|
247
|
+
tickSize: m.quote_tick.toString(),
|
|
248
|
+
fundingInterval: m.funding_period,
|
|
249
|
+
isActive: true,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async getMarket(symbol) {
|
|
254
|
+
const markets = await this.getMarkets();
|
|
255
|
+
const normalized = this.normalizeSymbol(symbol);
|
|
256
|
+
return markets.find((m) => m.symbol === normalized) ?? null;
|
|
257
|
+
}
|
|
258
|
+
async getTicker(market) {
|
|
259
|
+
const orderlySymbol = this.toOrderlySymbol(market);
|
|
260
|
+
const markPriceData = await this.fetchPublic(`/public/futures/${orderlySymbol}`);
|
|
261
|
+
if (!markPriceData.success) {
|
|
262
|
+
throw new Error(`Market ${market} not found`);
|
|
263
|
+
}
|
|
264
|
+
const mp = markPriceData.data;
|
|
265
|
+
const change24h = mp["24h_close"] && mp["24h_open"]
|
|
266
|
+
? (((mp["24h_close"] - mp["24h_open"]) / mp["24h_open"]) * 100).toFixed(2)
|
|
267
|
+
: "0";
|
|
268
|
+
// Fetch real bid/ask from orderbook
|
|
269
|
+
let bid = mp.mark_price.toString();
|
|
270
|
+
let ask = mp.mark_price.toString();
|
|
271
|
+
try {
|
|
272
|
+
const book = await this.fetchPublic(`/public/orderbook/${orderlySymbol}?max_level=1`);
|
|
273
|
+
const data = pickObject(book, ["data", "result", "results.0"]) ?? {};
|
|
274
|
+
const bids = this.toBookLevels(pickUnknown(data, ["bids"]));
|
|
275
|
+
const asks = this.toBookLevels(pickUnknown(data, ["asks"]));
|
|
276
|
+
if (bids.length > 0)
|
|
277
|
+
bid = bids[0].price;
|
|
278
|
+
if (asks.length > 0)
|
|
279
|
+
ask = asks[0].price;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// Fall back to mark price if orderbook unavailable
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
market: this.toStandardSymbol(orderlySymbol),
|
|
286
|
+
lastPrice: mp["24h_close"]?.toString() || mp.mark_price.toString(),
|
|
287
|
+
markPrice: mp.mark_price.toString(),
|
|
288
|
+
indexPrice: mp.index_price.toString(),
|
|
289
|
+
bid,
|
|
290
|
+
ask,
|
|
291
|
+
volume24h: mp["24h_amount"]?.toString() || "0",
|
|
292
|
+
change24h,
|
|
293
|
+
high24h: mp["24h_high"]?.toString() || "0",
|
|
294
|
+
low24h: mp["24h_low"]?.toString() || "0",
|
|
295
|
+
openInterest: mp.open_interest.toString(),
|
|
296
|
+
fundingRate: mp.est_funding_rate.toString(),
|
|
297
|
+
timestamp: Date.now(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async getTickers() {
|
|
301
|
+
const markets = await this.fetchMarkets();
|
|
302
|
+
// Use batch public/futures endpoint to avoid N+1 requests
|
|
303
|
+
const tickers = [];
|
|
304
|
+
try {
|
|
305
|
+
const response = await this.fetchPublic("/public/futures");
|
|
306
|
+
if (response.success && response.data?.rows) {
|
|
307
|
+
const markPriceMap = new Map();
|
|
308
|
+
for (const mp of response.data.rows) {
|
|
309
|
+
markPriceMap.set(mp.symbol, mp);
|
|
310
|
+
}
|
|
311
|
+
for (const m of markets) {
|
|
312
|
+
const mp = markPriceMap.get(m.symbol);
|
|
313
|
+
if (!mp)
|
|
314
|
+
continue;
|
|
315
|
+
const change24h = mp["24h_close"] && mp["24h_open"]
|
|
316
|
+
? (((mp["24h_close"] - mp["24h_open"]) / mp["24h_open"]) * 100).toFixed(2)
|
|
317
|
+
: "0";
|
|
318
|
+
tickers.push({
|
|
319
|
+
market: this.toStandardSymbol(m.symbol),
|
|
320
|
+
lastPrice: mp["24h_close"]?.toString() || mp.mark_price.toString(),
|
|
321
|
+
markPrice: mp.mark_price.toString(),
|
|
322
|
+
indexPrice: mp.index_price.toString(),
|
|
323
|
+
bid: mp.mark_price.toString(),
|
|
324
|
+
ask: mp.mark_price.toString(),
|
|
325
|
+
volume24h: mp["24h_amount"]?.toString() || "0",
|
|
326
|
+
change24h,
|
|
327
|
+
high24h: mp["24h_high"]?.toString() || "0",
|
|
328
|
+
low24h: mp["24h_low"]?.toString() || "0",
|
|
329
|
+
openInterest: mp.open_interest.toString(),
|
|
330
|
+
fundingRate: mp.est_funding_rate.toString(),
|
|
331
|
+
timestamp: Date.now(),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return tickers;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
// Fall back to individual fetches
|
|
339
|
+
}
|
|
340
|
+
// Fallback: sequential per-market
|
|
341
|
+
for (const m of markets.slice(0, 80)) {
|
|
342
|
+
try {
|
|
343
|
+
tickers.push(await this.getTicker(m.symbol));
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Best effort across all symbols.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return tickers;
|
|
350
|
+
}
|
|
351
|
+
async getOrderBook(market, depth = 20) {
|
|
352
|
+
const orderlySymbol = this.toOrderlySymbol(market);
|
|
353
|
+
// Orderly has a public orderbook endpoint
|
|
354
|
+
const response = this.canUseReadAuth()
|
|
355
|
+
? await this.fetchPrivate({
|
|
356
|
+
method: "GET",
|
|
357
|
+
endpoint: `/orderbook/${orderlySymbol}`,
|
|
358
|
+
query: { max_level: depth },
|
|
359
|
+
})
|
|
360
|
+
: await this.fetchPublic(`/public/orderbook/${orderlySymbol}?max_level=${depth}`);
|
|
361
|
+
const data = pickObject(response, ["data", "result", "results.0"]) ?? {};
|
|
362
|
+
const bids = this.toBookLevels(pickUnknown(data, ["bids"]));
|
|
363
|
+
const asks = this.toBookLevels(pickUnknown(data, ["asks"]));
|
|
364
|
+
return {
|
|
365
|
+
market: this.toStandardSymbol(orderlySymbol),
|
|
366
|
+
bids: bids.slice(0, depth),
|
|
367
|
+
asks: asks.slice(0, depth),
|
|
368
|
+
timestamp: toTimestampMs(pickUnknown(data, ["timestamp", "updated_time"]), Date.now()),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async getFundingRate(market) {
|
|
372
|
+
const orderlySymbol = this.toOrderlySymbol(market);
|
|
373
|
+
const markPriceData = await this.fetchPublic(`/public/futures/${orderlySymbol}`);
|
|
374
|
+
if (!markPriceData.success) {
|
|
375
|
+
throw new Error(`Funding rate not available for ${market}`);
|
|
376
|
+
}
|
|
377
|
+
const mp = markPriceData.data;
|
|
378
|
+
return {
|
|
379
|
+
market: this.toStandardSymbol(orderlySymbol),
|
|
380
|
+
rate: mp.est_funding_rate.toString(),
|
|
381
|
+
nextFundingTime: mp.next_funding_time,
|
|
382
|
+
timestamp: Date.now(),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
async getFundingRates() {
|
|
386
|
+
// Use batch futures endpoint to get all funding rates at once
|
|
387
|
+
try {
|
|
388
|
+
const response = await this.fetchPublic("/public/futures");
|
|
389
|
+
if (response.success && response.data?.rows) {
|
|
390
|
+
return response.data.rows.map((mp) => ({
|
|
391
|
+
market: this.toStandardSymbol(mp.symbol),
|
|
392
|
+
rate: mp.est_funding_rate.toString(),
|
|
393
|
+
nextFundingTime: mp.next_funding_time,
|
|
394
|
+
timestamp: Date.now(),
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
// Fall back to individual fetches
|
|
400
|
+
}
|
|
401
|
+
const rates = [];
|
|
402
|
+
for (const market of MAJOR_MARKETS) {
|
|
403
|
+
try {
|
|
404
|
+
rates.push(await this.getFundingRate(market));
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
// Best effort across majors.
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return rates;
|
|
411
|
+
}
|
|
412
|
+
// --------------------------------------------------------------------------
|
|
413
|
+
// Account Data (requires auth)
|
|
414
|
+
// --------------------------------------------------------------------------
|
|
415
|
+
async getPositions() {
|
|
416
|
+
const response = await this.fetchPrivate({
|
|
417
|
+
method: "GET",
|
|
418
|
+
endpoint: "/positions",
|
|
419
|
+
});
|
|
420
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
421
|
+
return rows
|
|
422
|
+
.map((row) => {
|
|
423
|
+
const symbol = this.toStandardSymbol(pickString(row, ["symbol", "market", "instrument_name"]) ?? "");
|
|
424
|
+
if (!symbol)
|
|
425
|
+
return null;
|
|
426
|
+
const quantity = pickNumber(row, ["position_qty", "quantity", "size", "holding"], 0);
|
|
427
|
+
if (quantity === 0)
|
|
428
|
+
return null;
|
|
429
|
+
const side = quantity < 0 ? "short" : "long";
|
|
430
|
+
const absSize = Math.abs(quantity);
|
|
431
|
+
return {
|
|
432
|
+
market: symbol,
|
|
433
|
+
side,
|
|
434
|
+
size: absSize.toString(),
|
|
435
|
+
entryPrice: pickString(row, ["average_open_price", "entry_price", "avg_price"]) ?? "0",
|
|
436
|
+
markPrice: pickString(row, ["mark_price", "close_price", "last_price"]) ?? "0",
|
|
437
|
+
liquidationPrice: pickString(row, ["est_liq_price", "liquidation_price"]) ?? null,
|
|
438
|
+
unrealizedPnl: pickString(row, ["unrealized_pnl", "pnl_24_h"]) ?? "0",
|
|
439
|
+
realizedPnl: pickString(row, ["realized_pnl"]) ?? "0",
|
|
440
|
+
leverage: pickNumber(row, ["leverage"], 1),
|
|
441
|
+
marginType: "cross",
|
|
442
|
+
margin: pickString(row, ["position_margin", "initial_margin"]) ?? "0",
|
|
443
|
+
timestamp: toTimestampMs(pickUnknown(row, ["updated_time", "created_time", "timestamp"]), Date.now()),
|
|
444
|
+
};
|
|
445
|
+
})
|
|
446
|
+
.filter((row) => row !== null);
|
|
447
|
+
}
|
|
448
|
+
async getPosition(market) {
|
|
449
|
+
const positions = await this.getPositions();
|
|
450
|
+
const symbol = this.normalizeSymbol(market);
|
|
451
|
+
return positions.find((p) => p.market === symbol) ?? null;
|
|
452
|
+
}
|
|
453
|
+
async getOrders(market) {
|
|
454
|
+
const response = await this.fetchPrivate({
|
|
455
|
+
method: "GET",
|
|
456
|
+
endpoint: "/orders",
|
|
457
|
+
query: {
|
|
458
|
+
symbol: market ? this.toOrderlySymbol(market) : undefined,
|
|
459
|
+
size: 500,
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
463
|
+
return rows
|
|
464
|
+
.map((row) => this.toOrder(row))
|
|
465
|
+
.filter((row) => row !== null);
|
|
466
|
+
}
|
|
467
|
+
async getOrder(orderId) {
|
|
468
|
+
const response = await this.fetchPrivate({
|
|
469
|
+
method: "GET",
|
|
470
|
+
endpoint: `/order/${encodeURIComponent(orderId)}`,
|
|
471
|
+
});
|
|
472
|
+
const row = pickObject(response, ["data", "result", "results.0"]) ??
|
|
473
|
+
pickArray(response, ["rows", "data.rows"])[0] ??
|
|
474
|
+
null;
|
|
475
|
+
if (row) {
|
|
476
|
+
const mapped = this.toOrder(row);
|
|
477
|
+
if (mapped)
|
|
478
|
+
return mapped;
|
|
479
|
+
}
|
|
480
|
+
const orders = await this.getOrders();
|
|
481
|
+
return orders.find((o) => o.id === orderId) ?? null;
|
|
482
|
+
}
|
|
483
|
+
async getBalances() {
|
|
484
|
+
const response = await this.fetchPrivate({
|
|
485
|
+
method: "GET",
|
|
486
|
+
endpoint: "/client/holding",
|
|
487
|
+
query: { all: "true" },
|
|
488
|
+
});
|
|
489
|
+
// Fetch positions to compute unrealized PnL
|
|
490
|
+
let totalUnrealizedPnl = 0;
|
|
491
|
+
let totalMarginUsed = 0;
|
|
492
|
+
try {
|
|
493
|
+
const positions = await this.getPositions();
|
|
494
|
+
for (const p of positions) {
|
|
495
|
+
totalUnrealizedPnl += parseFloat(p.unrealizedPnl || "0");
|
|
496
|
+
totalMarginUsed += parseFloat(p.margin || "0");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// Best effort - continue without position data
|
|
501
|
+
}
|
|
502
|
+
const rows = pickArray(response, ["data.holding", "data.rows", "rows", "results"]);
|
|
503
|
+
if (rows.length > 0) {
|
|
504
|
+
return rows.map((row) => {
|
|
505
|
+
const holding = pickNumber(row, ["holding", "balance"], 0);
|
|
506
|
+
const frozen = pickNumber(row, ["frozen", "locked"], 0);
|
|
507
|
+
const total = holding + frozen;
|
|
508
|
+
return {
|
|
509
|
+
asset: pickString(row, ["token", "asset", "currency"]) ?? "USDC",
|
|
510
|
+
total: total.toString(),
|
|
511
|
+
available: holding.toString(),
|
|
512
|
+
locked: frozen.toString(),
|
|
513
|
+
unrealizedPnl: totalUnrealizedPnl.toString(),
|
|
514
|
+
marginUsed: totalMarginUsed > 0 ? totalMarginUsed.toString() : frozen.toString(),
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const info = await this.fetchPrivate({ method: "GET", endpoint: "/client/info" });
|
|
519
|
+
const data = pickObject(info, ["data", "result", "results.0"]) ?? {};
|
|
520
|
+
const total = pickString(data, ["equity", "balance"]) ?? "0";
|
|
521
|
+
const available = pickString(data, ["free_collateral", "available_balance"]) ?? total;
|
|
522
|
+
return [
|
|
523
|
+
{
|
|
524
|
+
asset: "USDC",
|
|
525
|
+
total,
|
|
526
|
+
available,
|
|
527
|
+
locked: totalMarginUsed > 0 ? totalMarginUsed.toString() : "0",
|
|
528
|
+
unrealizedPnl: totalUnrealizedPnl.toString(),
|
|
529
|
+
marginUsed: totalMarginUsed > 0 ? totalMarginUsed.toString() : "0",
|
|
530
|
+
},
|
|
531
|
+
];
|
|
532
|
+
}
|
|
533
|
+
async getTrades(market, limit = 100) {
|
|
534
|
+
const response = await this.fetchPrivate({
|
|
535
|
+
method: "GET",
|
|
536
|
+
endpoint: "/trades",
|
|
537
|
+
query: {
|
|
538
|
+
symbol: market ? this.toOrderlySymbol(market) : undefined,
|
|
539
|
+
size: Math.max(1, Math.min(limit, 500)),
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
543
|
+
return rows
|
|
544
|
+
.map((row) => {
|
|
545
|
+
const symbol = this.toStandardSymbol(pickString(row, ["symbol"]) ?? "");
|
|
546
|
+
if (!symbol)
|
|
547
|
+
return null;
|
|
548
|
+
const sideRaw = pickString(row, ["side"])?.toLowerCase() ?? "buy";
|
|
549
|
+
const side = sideRaw === "sell" ? "short" : "long";
|
|
550
|
+
const orderId = pickString(row, ["order_id"]);
|
|
551
|
+
return {
|
|
552
|
+
id: pickString(row, ["id", "trade_id", "execution_id"]) ?? `${symbol}-${Date.now()}`,
|
|
553
|
+
market: symbol,
|
|
554
|
+
side,
|
|
555
|
+
price: pickString(row, ["executed_price", "price"]) ?? "0",
|
|
556
|
+
size: pickString(row, ["executed_quantity", "size", "quantity"]) ?? "0",
|
|
557
|
+
fee: pickString(row, ["fee", "total_fee"]) ?? "0",
|
|
558
|
+
feeAsset: pickString(row, ["fee_asset", "fee_currency"]) ?? "USDC",
|
|
559
|
+
timestamp: toTimestampMs(pickUnknown(row, ["executed_timestamp", "created_time", "timestamp"]), Date.now()),
|
|
560
|
+
...(orderId ? { orderId } : {}),
|
|
561
|
+
};
|
|
562
|
+
})
|
|
563
|
+
.filter((row) => row !== null);
|
|
564
|
+
}
|
|
565
|
+
// --------------------------------------------------------------------------
|
|
566
|
+
// Trading
|
|
567
|
+
// --------------------------------------------------------------------------
|
|
568
|
+
async placeOrder(params) {
|
|
569
|
+
const symbol = this.toOrderlySymbol(params.market);
|
|
570
|
+
const payload = {
|
|
571
|
+
symbol,
|
|
572
|
+
side: params.side === "long" ? "BUY" : "SELL",
|
|
573
|
+
order_type: this.toOrderlyOrderType(params.type),
|
|
574
|
+
order_quantity: params.size,
|
|
575
|
+
client_order_id: params.clientOrderId,
|
|
576
|
+
};
|
|
577
|
+
if (params.type !== "market") {
|
|
578
|
+
if (!params.price) {
|
|
579
|
+
throw new Error("Order price is required for non-market orders");
|
|
580
|
+
}
|
|
581
|
+
payload.order_price = params.price;
|
|
582
|
+
}
|
|
583
|
+
const response = await this.fetchPrivate({
|
|
584
|
+
method: "POST",
|
|
585
|
+
endpoint: "/order",
|
|
586
|
+
body: payload,
|
|
587
|
+
signedPayload: payload,
|
|
588
|
+
});
|
|
589
|
+
const data = pickObject(response, ["data", "result", "results.0"]) ?? payload;
|
|
590
|
+
const mapped = this.toOrder(data);
|
|
591
|
+
if (mapped) {
|
|
592
|
+
return mapped;
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
id: pickString(data, ["order_id", "id", "client_order_id"]) ?? `${Date.now()}`,
|
|
596
|
+
market: this.toStandardSymbol(symbol),
|
|
597
|
+
side: params.side,
|
|
598
|
+
type: params.type,
|
|
599
|
+
size: params.size,
|
|
600
|
+
price: params.price ?? null,
|
|
601
|
+
filled: "0",
|
|
602
|
+
remaining: params.size,
|
|
603
|
+
status: "open",
|
|
604
|
+
reduceOnly: params.reduceOnly ?? false,
|
|
605
|
+
postOnly: params.postOnly ?? false,
|
|
606
|
+
timestamp: Date.now(),
|
|
607
|
+
triggerPrice: params.triggerPrice,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async cancelOrder(params) {
|
|
611
|
+
const payload = {};
|
|
612
|
+
if (params.orderId) {
|
|
613
|
+
payload.order_id = params.orderId;
|
|
614
|
+
}
|
|
615
|
+
if (params.clientOrderId) {
|
|
616
|
+
payload.client_order_id = params.clientOrderId;
|
|
617
|
+
}
|
|
618
|
+
if (Object.keys(payload).length === 0) {
|
|
619
|
+
throw new Error("cancelOrder requires orderId or clientOrderId");
|
|
620
|
+
}
|
|
621
|
+
await this.fetchPrivate({
|
|
622
|
+
method: "DELETE",
|
|
623
|
+
endpoint: "/order",
|
|
624
|
+
query: payload,
|
|
625
|
+
signedPayload: payload,
|
|
626
|
+
});
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
async cancelAllOrders(market) {
|
|
630
|
+
const payload = {};
|
|
631
|
+
if (market) {
|
|
632
|
+
payload.symbol = this.toOrderlySymbol(market);
|
|
633
|
+
}
|
|
634
|
+
const response = await this.fetchPrivate({
|
|
635
|
+
method: "DELETE",
|
|
636
|
+
endpoint: "/orders",
|
|
637
|
+
query: payload,
|
|
638
|
+
signedPayload: payload,
|
|
639
|
+
});
|
|
640
|
+
const count = pickNumber(response, ["data.count", "count", "cancelled"], NaN);
|
|
641
|
+
if (Number.isFinite(count) && count >= 0) {
|
|
642
|
+
return Math.floor(count);
|
|
643
|
+
}
|
|
644
|
+
return market ? 1 : 0;
|
|
645
|
+
}
|
|
646
|
+
async setLeverage(_market, leverage) {
|
|
647
|
+
const payload = { leverage };
|
|
648
|
+
await this.fetchPrivate({
|
|
649
|
+
method: "POST",
|
|
650
|
+
endpoint: "/client/leverage",
|
|
651
|
+
body: payload,
|
|
652
|
+
signedPayload: payload,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
async setMarginType(_market, type) {
|
|
656
|
+
if (type !== "cross") {
|
|
657
|
+
throw new Error("Orderly currently supports cross margin only");
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// --------------------------------------------------------------------------
|
|
661
|
+
// Advanced Trading
|
|
662
|
+
// --------------------------------------------------------------------------
|
|
663
|
+
async modifyOrder(_params) {
|
|
664
|
+
throw new Error("Orderly does not support order modification. Cancel and replace instead.");
|
|
665
|
+
}
|
|
666
|
+
async batchPlaceOrders(paramsList) {
|
|
667
|
+
// Orderly has batch create order endpoint
|
|
668
|
+
const results = [];
|
|
669
|
+
for (const params of paramsList) {
|
|
670
|
+
results.push(await this.placeOrder(params));
|
|
671
|
+
}
|
|
672
|
+
return results;
|
|
673
|
+
}
|
|
674
|
+
async batchCancelOrders(paramsList) {
|
|
675
|
+
const results = [];
|
|
676
|
+
for (const params of paramsList) {
|
|
677
|
+
try {
|
|
678
|
+
results.push(await this.cancelOrder(params));
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
results.push(false);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return results;
|
|
685
|
+
}
|
|
686
|
+
async cancelAllAfter(timeoutMs) {
|
|
687
|
+
// Orderly cancel_all_after endpoint
|
|
688
|
+
const payload = {
|
|
689
|
+
cancel_after: Math.max(0, Math.floor(timeoutMs / 1000)),
|
|
690
|
+
};
|
|
691
|
+
await this.fetchPrivate({
|
|
692
|
+
method: "POST",
|
|
693
|
+
endpoint: "/cancel_all_after",
|
|
694
|
+
body: payload,
|
|
695
|
+
signedPayload: payload,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
async getOrderHistory(market, limit = 100) {
|
|
699
|
+
const response = await this.fetchPrivate({
|
|
700
|
+
method: "GET",
|
|
701
|
+
endpoint: "/orders",
|
|
702
|
+
query: {
|
|
703
|
+
symbol: market ? this.toOrderlySymbol(market) : undefined,
|
|
704
|
+
size: Math.max(1, Math.min(limit, 500)),
|
|
705
|
+
status: "COMPLETED",
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
709
|
+
return rows
|
|
710
|
+
.map((row) => this.toOrder(row))
|
|
711
|
+
.filter((row) => row !== null);
|
|
712
|
+
}
|
|
713
|
+
async getFundingHistory(market, limit = 100) {
|
|
714
|
+
const response = await this.fetchPrivate({
|
|
715
|
+
method: "GET",
|
|
716
|
+
endpoint: "/funding_fee/history",
|
|
717
|
+
query: {
|
|
718
|
+
symbol: market ? this.toOrderlySymbol(market) : undefined,
|
|
719
|
+
size: Math.max(1, Math.min(limit, 500)),
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
723
|
+
return rows.map((row) => ({
|
|
724
|
+
market: this.toStandardSymbol(pickString(row, ["symbol"]) ?? ""),
|
|
725
|
+
amount: pickString(row, ["funding_fee", "fee", "payment"]) ?? "0",
|
|
726
|
+
rate: pickString(row, ["funding_rate", "rate"]) ?? "0",
|
|
727
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_time", "timestamp"]), Date.now()),
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
async getPublicTrades(market, limit = 100) {
|
|
731
|
+
const orderlySymbol = this.toOrderlySymbol(market);
|
|
732
|
+
const response = await this.fetchPublic(`/public/market_trades?symbol=${orderlySymbol}&limit=${Math.min(limit, 100)}`);
|
|
733
|
+
const rows = pickArray(response, ["data.rows", "rows", "data", "results"]);
|
|
734
|
+
return rows.map((row) => ({
|
|
735
|
+
id: pickString(row, ["id", "trade_id"]) ?? `${Date.now()}`,
|
|
736
|
+
market: this.toStandardSymbol(orderlySymbol),
|
|
737
|
+
side: (pickString(row, ["side"])?.toLowerCase() === "sell" ? "short" : "long"),
|
|
738
|
+
price: pickString(row, ["executed_price", "price"]) ?? "0",
|
|
739
|
+
size: pickString(row, ["executed_quantity", "size", "quantity"]) ?? "0",
|
|
740
|
+
timestamp: toTimestampMs(pickUnknown(row, ["executed_timestamp", "timestamp"]), Date.now()),
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
// --------------------------------------------------------------------------
|
|
744
|
+
// Market Maker Protection
|
|
745
|
+
// --------------------------------------------------------------------------
|
|
746
|
+
async setMMP(_config) {
|
|
747
|
+
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
748
|
+
}
|
|
749
|
+
async getMMP(_market) {
|
|
750
|
+
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
751
|
+
}
|
|
752
|
+
async resetMMP(_market) {
|
|
753
|
+
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
754
|
+
}
|
|
755
|
+
// --------------------------------------------------------------------------
|
|
756
|
+
// TWAP Orders
|
|
757
|
+
// --------------------------------------------------------------------------
|
|
758
|
+
async placeTWAP(_params) {
|
|
759
|
+
throw new Error("Orderly does not support TWAP orders");
|
|
760
|
+
}
|
|
761
|
+
async cancelTWAP(_twapId) {
|
|
762
|
+
throw new Error("Orderly does not support TWAP orders");
|
|
763
|
+
}
|
|
764
|
+
async getTWAPStatus(_twapId) {
|
|
765
|
+
throw new Error("Orderly does not support TWAP orders");
|
|
766
|
+
}
|
|
767
|
+
// --------------------------------------------------------------------------
|
|
768
|
+
// Margin Management
|
|
769
|
+
// --------------------------------------------------------------------------
|
|
770
|
+
async updateIsolatedMargin(_market, _amount) {
|
|
771
|
+
throw new Error("Orderly does not support isolated margin adjustment");
|
|
772
|
+
}
|
|
773
|
+
// --------------------------------------------------------------------------
|
|
774
|
+
// Subscriptions (polling)
|
|
775
|
+
// --------------------------------------------------------------------------
|
|
776
|
+
subscribe(callbacks) {
|
|
777
|
+
const timer = setInterval(async () => {
|
|
778
|
+
try {
|
|
779
|
+
if (callbacks.onPositions)
|
|
780
|
+
callbacks.onPositions(await this.getPositions());
|
|
781
|
+
if (callbacks.onOrders)
|
|
782
|
+
callbacks.onOrders(await this.getOrders());
|
|
783
|
+
if (callbacks.onBalances)
|
|
784
|
+
callbacks.onBalances(await this.getBalances());
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
788
|
+
}
|
|
789
|
+
}, 3000);
|
|
790
|
+
this.subscriptionTimers.add(timer);
|
|
791
|
+
return () => {
|
|
792
|
+
clearInterval(timer);
|
|
793
|
+
this.subscriptionTimers.delete(timer);
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
subscribeOrderBook(market, callback) {
|
|
797
|
+
const normalized = this.normalizeSymbol(market);
|
|
798
|
+
const timer = setInterval(async () => {
|
|
799
|
+
try {
|
|
800
|
+
callback(await this.getOrderBook(normalized));
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
// Ignore transient polling errors.
|
|
804
|
+
}
|
|
805
|
+
}, 1500);
|
|
806
|
+
this.subscriptionTimers.add(timer);
|
|
807
|
+
return () => {
|
|
808
|
+
clearInterval(timer);
|
|
809
|
+
this.subscriptionTimers.delete(timer);
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
subscribeTicker(market, callback) {
|
|
813
|
+
const normalized = this.normalizeSymbol(market);
|
|
814
|
+
const timer = setInterval(async () => {
|
|
815
|
+
try {
|
|
816
|
+
callback(await this.getTicker(normalized));
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
// Ignore transient polling errors.
|
|
820
|
+
}
|
|
821
|
+
}, 1200);
|
|
822
|
+
this.subscriptionTimers.add(timer);
|
|
823
|
+
return () => {
|
|
824
|
+
clearInterval(timer);
|
|
825
|
+
this.subscriptionTimers.delete(timer);
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
// --------------------------------------------------------------------------
|
|
829
|
+
// Private Helpers
|
|
830
|
+
// --------------------------------------------------------------------------
|
|
831
|
+
toOrderlyOrderType(type) {
|
|
832
|
+
if (type === "market")
|
|
833
|
+
return "MARKET";
|
|
834
|
+
return "LIMIT";
|
|
835
|
+
}
|
|
836
|
+
toOrderlySymbol(symbol) {
|
|
837
|
+
const upper = symbol.toUpperCase().trim();
|
|
838
|
+
if (upper.startsWith("PERP_"))
|
|
839
|
+
return upper;
|
|
840
|
+
const base = upper
|
|
841
|
+
.replace("-USD-PERP", "")
|
|
842
|
+
.replace("-PERP", "")
|
|
843
|
+
.replace("/USDC", "")
|
|
844
|
+
.replace("/USD", "");
|
|
845
|
+
return `PERP_${base}_USDC`;
|
|
846
|
+
}
|
|
847
|
+
toStandardSymbol(orderlySymbol) {
|
|
848
|
+
const parts = orderlySymbol.split("_");
|
|
849
|
+
if (parts.length >= 2 && parts[0] === "PERP") {
|
|
850
|
+
return `${parts[1]}-PERP`;
|
|
851
|
+
}
|
|
852
|
+
if (!orderlySymbol)
|
|
853
|
+
return "";
|
|
854
|
+
const normalized = orderlySymbol.toUpperCase();
|
|
855
|
+
if (normalized.endsWith("-PERP"))
|
|
856
|
+
return normalized;
|
|
857
|
+
return `${normalized}-PERP`;
|
|
858
|
+
}
|
|
859
|
+
normalizeSymbol(symbol) {
|
|
860
|
+
const upper = symbol.toUpperCase().trim();
|
|
861
|
+
if (!upper)
|
|
862
|
+
return "";
|
|
863
|
+
if (upper.startsWith("PERP_")) {
|
|
864
|
+
return this.toStandardSymbol(upper);
|
|
865
|
+
}
|
|
866
|
+
if (upper.endsWith("-PERP"))
|
|
867
|
+
return upper;
|
|
868
|
+
return `${upper}-PERP`;
|
|
869
|
+
}
|
|
870
|
+
toOrder(row) {
|
|
871
|
+
const market = this.toStandardSymbol(pickString(row, ["symbol", "market", "instrument_name"]) ?? "");
|
|
872
|
+
if (!market)
|
|
873
|
+
return null;
|
|
874
|
+
const sideRaw = pickString(row, ["side"])?.toLowerCase() ?? "buy";
|
|
875
|
+
const side = sideRaw === "sell" ? "short" : "long";
|
|
876
|
+
const size = pickString(row, ["quantity", "order_quantity", "size"]) ?? "0";
|
|
877
|
+
const filled = pickString(row, ["executed", "executed_quantity", "filled"]) ?? "0";
|
|
878
|
+
const remaining = pickString(row, ["remaining", "leaves_qty"]) ??
|
|
879
|
+
Math.max(0, parseFloat(size) - parseFloat(filled)).toString();
|
|
880
|
+
const statusRaw = pickString(row, ["status"])?.toLowerCase() ?? "new";
|
|
881
|
+
const status = statusRaw.includes("cancel")
|
|
882
|
+
? "cancelled"
|
|
883
|
+
: statusRaw.includes("filled")
|
|
884
|
+
? parseFloat(remaining) > 0
|
|
885
|
+
? "partial"
|
|
886
|
+
: "filled"
|
|
887
|
+
: statusRaw.includes("reject") || statusRaw.includes("expire")
|
|
888
|
+
? "expired"
|
|
889
|
+
: "open";
|
|
890
|
+
const typeRaw = pickString(row, ["type", "order_type"])?.toLowerCase() ?? "limit";
|
|
891
|
+
const type = typeRaw.includes("market") ? "market" : "limit";
|
|
892
|
+
return {
|
|
893
|
+
id: pickString(row, ["order_id", "id", "client_order_id"]) ?? `${Date.now()}`,
|
|
894
|
+
market,
|
|
895
|
+
side,
|
|
896
|
+
type,
|
|
897
|
+
size,
|
|
898
|
+
price: pickString(row, ["price", "order_price"]),
|
|
899
|
+
filled,
|
|
900
|
+
remaining,
|
|
901
|
+
status,
|
|
902
|
+
reduceOnly: pickBoolean(row, ["reduce_only", "reduce_only_order"], false),
|
|
903
|
+
postOnly: pickBoolean(row, ["post_only"], false),
|
|
904
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_time", "updated_time", "timestamp"]), Date.now()),
|
|
905
|
+
triggerPrice: pickString(row, ["trigger_price"]) ?? undefined,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
toBookLevels(value) {
|
|
909
|
+
if (!Array.isArray(value))
|
|
910
|
+
return [];
|
|
911
|
+
const levels = [];
|
|
912
|
+
for (const row of value) {
|
|
913
|
+
if (!Array.isArray(row) || row.length < 2)
|
|
914
|
+
continue;
|
|
915
|
+
const [price, size] = row;
|
|
916
|
+
const priceStr = stringifyValue(price);
|
|
917
|
+
const sizeStr = stringifyValue(size);
|
|
918
|
+
levels.push({ price: priceStr, size: sizeStr });
|
|
919
|
+
}
|
|
920
|
+
return levels;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function stripHexPrefix(value) {
|
|
924
|
+
return value.startsWith("0x") ? value.slice(2) : value;
|
|
925
|
+
}
|
|
926
|
+
function stringifyValue(value) {
|
|
927
|
+
if (typeof value === "string")
|
|
928
|
+
return value;
|
|
929
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
930
|
+
return String(value);
|
|
931
|
+
}
|
|
932
|
+
return "";
|
|
933
|
+
}
|
|
934
|
+
// Register the adapter
|
|
935
|
+
registerAdapter("orderly", () => new OrderlyAdapter());
|
|
936
|
+
export default OrderlyAdapter;
|