repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * Imperial Perps Agent — real Imperial Trading API client |
| #3 | * |
| #4 | * Base: https://api.imperial.space/api/v1 |
| #5 | * Auth: JWT obtained via connect → exchange flow (or pre-set IMPERIAL_JWT env var) |
| #6 | * |
| #7 | * Environment: |
| #8 | * IMPERIAL_JWT — pre-issued JWT (skip auth flow) |
| #9 | * IMPERIAL_WALLET — operator wallet pubkey (base58, never private key) |
| #10 | * IMPERIAL_PROFILE_INDEX — subaccount index 0..5 (default 0) |
| #11 | * IMPERIAL_LIVE — "true" to enable live submission |
| #12 | * IMPERIAL_MAX_SIZE_USD — hard cap per order in USD (default 100) |
| #13 | * IMPERIAL_ALLOWED_SYMS — comma-separated allowlist (default SOL,ETH,BTC) |
| #14 | * IMPERIAL_SLIPPAGE_BPS — default slippage (default 50) |
| #15 | */ |
| #16 | export const IMPERIAL_BASE = "https://api.imperial.space/api/v1"; |
| #17 | export const Underwriter = { |
| #18 | Jupiter: 0, |
| #19 | Flash: 1, |
| #20 | Phoenix: 2, |
| #21 | GMTrade: 3, |
| #22 | }; |
| #23 | export const UNDERWRITER_LABELS = { |
| #24 | 0: "Jupiter", |
| #25 | 1: "Flash Trade", |
| #26 | 2: "Phoenix", |
| #27 | 3: "GMTrade", |
| #28 | }; |
| #29 | export const ORDER_TYPE_NAMES = { |
| #30 | 0: "Market", 1: "Limit", 2: "StopLimit", 3: "LandMine", |
| #31 | 4: "Ratchet", 6: "RatchetEntry", 9: "DCA", 10: "FibRatchet", |
| #32 | 11: "FibRatchetEntry", 12: "DcaClose", 13: "DcaTimeClose", |
| #33 | 14: "DcaRatchetClose", 15: "DcaTime", 16: "DcaRatchet", |
| #34 | }; |
| #35 | export function loadImperialConfig(env = process.env) { |
| #36 | return { |
| #37 | jwt: env.IMPERIAL_JWT ?? "", |
| #38 | wallet: env.IMPERIAL_WALLET ?? "", |
| #39 | profileIndex: Number(env.IMPERIAL_PROFILE_INDEX ?? 0), |
| #40 | live: env.IMPERIAL_LIVE === "true", |
| #41 | maxSizeUsd: Number(env.IMPERIAL_MAX_SIZE_USD ?? 100), |
| #42 | allowedSymbols: (env.IMPERIAL_ALLOWED_SYMS ?? "SOL,ETH,BTC") |
| #43 | .split(",") |
| #44 | .map((s) => s.trim().toUpperCase()) |
| #45 | .filter(Boolean), |
| #46 | slippageBps: Number(env.IMPERIAL_SLIPPAGE_BPS ?? 50), |
| #47 | base: env.IMPERIAL_API_BASE ?? IMPERIAL_BASE, |
| #48 | }; |
| #49 | } |
| #50 | // ─── HTTP helpers ───────────────────────────────────────────────────────────── |
| #51 | /** sizeUsd in human dollars → 6-decimal fixed point (1_000_000 = $1) */ |
| #52 | export function usdToFixed(usd) { |
| #53 | return Math.round(usd * 1_000_000); |
| #54 | } |
| #55 | /** 6-decimal fixed point → human dollars */ |
| #56 | export function fixedToUsd(fixed) { |
| #57 | return fixed / 1_000_000; |
| #58 | } |
| #59 | async function imperialGet(base, path, jwt) { |
| #60 | const res = await fetch(`${base}${path}`, { |
| #61 | headers: jwt ? { Authorization: `Bearer ${jwt}` } : {}, |
| #62 | }); |
| #63 | if (!res.ok) { |
| #64 | const body = await res.text().catch(() => ""); |
| #65 | throw new Error(`GET ${path} → ${res.status}: ${body}`); |
| #66 | } |
| #67 | return res.json(); |
| #68 | } |
| #69 | async function imperialPost(base, path, body, jwt) { |
| #70 | const res = await fetch(`${base}${path}`, { |
| #71 | method: "POST", |
| #72 | headers: { |
| #73 | "Content-Type": "application/json", |
| #74 | ...(jwt ? { Authorization: `Bearer ${jwt}` } : {}), |
| #75 | }, |
| #76 | body: JSON.stringify(body), |
| #77 | }); |
| #78 | if (!res.ok) { |
| #79 | const text = await res.text().catch(() => ""); |
| #80 | throw new Error(`POST ${path} → ${res.status}: ${text}`); |
| #81 | } |
| #82 | return res.json(); |
| #83 | } |
| #84 | function asArray(value, keys = []) { |
| #85 | if (Array.isArray(value)) |
| #86 | return value; |
| #87 | if (value && typeof value === "object") { |
| #88 | for (const key of keys) { |
| #89 | const nested = value[key]; |
| #90 | if (Array.isArray(nested)) |
| #91 | return nested; |
| #92 | } |
| #93 | } |
| #94 | return []; |
| #95 | } |
| #96 | export function scoreImperialMarket(snap) { |
| #97 | const scores = { momentum: 0, funding: 0, liquidity: 0 }; |
| #98 | // Funding: find Phoenix funding, average long rate |
| #99 | const phoenixFunding = snap.fundingRates.filter((r) => r.venue === "phoenix"); |
| #100 | if (phoenixFunding.length > 0) { |
| #101 | const avgLong = phoenixFunding.reduce((acc, r) => acc + (r.longFundingRatePerHourPercent ?? 0), 0) / |
| #102 | phoenixFunding.length; |
| #103 | // > 0 longs pay shorts → crowded long → sell bias |
| #104 | scores.funding = Math.max(-1, Math.min(1, -avgLong * 20)); |
| #105 | } |
| #106 | // Liquidity: Phoenix depth spread |
| #107 | const depth = snap.depth; |
| #108 | if ((depth?.bids?.length ?? 0) > 0 && (depth?.asks?.length ?? 0) > 0) { |
| #109 | const bid = depth?.bids[0]?.[0] ?? 0; |
| #110 | const ask = depth?.asks[0]?.[0] ?? 0; |
| #111 | if (bid > 0) { |
| #112 | const spreadBps = ((ask - bid) / bid) * 10000; |
| #113 | scores.liquidity = spreadBps < 10 ? 1 : spreadBps < 30 ? 0.5 : 0; |
| #114 | } |
| #115 | } |
| #116 | // Momentum: mark vs mid |
| #117 | if (snap.markPrice !== null && (depth?.bids?.length ?? 0) > 0 && (depth?.asks?.length ?? 0) > 0) { |
| #118 | const bid = depth?.bids[0]?.[0] ?? 0; |
| #119 | const ask = depth?.asks[0]?.[0] ?? 0; |
| #120 | if (bid > 0 && ask > 0) { |
| #121 | const mid = (bid + ask) / 2; |
| #122 | const drift = (snap.markPrice - mid) / mid; |
| #123 | scores.momentum = Math.max(-1, Math.min(1, -drift * 50)); |
| #124 | } |
| #125 | } |
| #126 | const composite = scores.momentum * 0.40 + |
| #127 | scores.funding * 0.40 + |
| #128 | scores.liquidity * 0.20; |
| #129 | const confidence = Math.min(1, Math.abs(composite)); |
| #130 | const THRESHOLD = 0.25; |
| #131 | const decision = composite > THRESHOLD ? "buy" : composite < -THRESHOLD ? "sell" : "watch"; |
| #132 | const phoenixRate = phoenixFunding[0]; |
| #133 | const parts = []; |
| #134 | if (phoenixRate?.longFundingRatePerHourPercent !== null && phoenixRate !== undefined) { |
| #135 | const ann = (phoenixRate.longFundingRatePerHourPercent ?? 0) * 8760; |
| #136 | parts.push(`funding ${ann.toFixed(1)}% ann`); |
| #137 | } |
| #138 | if (scores.liquidity > 0) { |
| #139 | parts.push(`liquidity ${scores.liquidity.toFixed(2)}`); |
| #140 | } |
| #141 | parts.push(`composite ${composite.toFixed(3)}`); |
| #142 | return { |
| #143 | symbol: snap.symbol, |
| #144 | decision, |
| #145 | confidence, |
| #146 | scores, |
| #147 | rationale: `${decision.toUpperCase()} — ${parts.join(" | ")}`, |
| #148 | snapshot: snap, |
| #149 | }; |
| #150 | } |
| #151 | function makeId() { |
| #152 | return `imp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; |
| #153 | } |
| #154 | // ─── Main client class ──────────────────────────────────────────────────────── |
| #155 | export class ImperialClient { |
| #156 | config; |
| #157 | constructor(config) { |
| #158 | this.config = { ...loadImperialConfig(), ...config }; |
| #159 | } |
| #160 | get jwt() { |
| #161 | return this.config.jwt; |
| #162 | } |
| #163 | get wallet() { |
| #164 | return this.config.wallet; |
| #165 | } |
| #166 | // ── Auth ── |
| #167 | /** |
| #168 | * Exchange a pre-signed connect code for a JWT. |
| #169 | * The caller must sign imperial:mobile-connect:{wallet}:{nonce} externally. |
| #170 | */ |
| #171 | async exchangeCode(code) { |
| #172 | const data = await imperialPost(this.config.base, "/mobile/exchange", { code }); |
| #173 | this.config.jwt = data.jwt; |
| #174 | return data; |
| #175 | } |
| #176 | /** Revoke the current JWT (call on bot shutdown). */ |
| #177 | async revokeJwt() { |
| #178 | if (!this.config.jwt) |
| #179 | return; |
| #180 | await imperialPost(this.config.base, "/mobile/revoke", { jwt: this.config.jwt }); |
| #181 | this.config.jwt = ""; |
| #182 | } |
| #183 | // ── Public reads (no auth) ── |
| #184 | async getStatus() { |
| #185 | return imperialGet(this.config.base, "/status"); |
| #186 | } |
| #187 | async getFundingRates() { |
| #188 | return imperialGet(this.config.base, "/funding-rates"); |
| #189 | } |
| #190 | async getMarkPrices() { |
| #191 | return imperialGet(this.config.base, "/mark-prices"); |
| #192 | } |
| #193 | async getPhoenixMarkPrices() { |
| #194 | return imperialGet(this.config.base, "/phoenix/mark-prices"); |
| #195 | } |
| #196 | async getPhoenixDepth(symbol) { |
| #197 | const path = symbol |
| #198 | ? `/phoenix/depth?symbol=${encodeURIComponent(symbol)}` |
| #199 | : "/phoenix/depth"; |
| #200 | return imperialGet(this.config.base, path); |
| #201 | } |
| #202 | async getPhoenixMarkets() { |
| #203 | return imperialGet(this.config.base, "/phoenix/markets"); |
| #204 | } |
| #205 | async getFlashMarkets() { |
| #206 | return imperialGet(this.config.base, "/flash/markets"); |
| #207 | } |
| #208 | async getGMTradeMarkets() { |
| #209 | return imperialGet(this.config.base, "/gmtrade/markets"); |
| #210 | } |
| #211 | async getGMTradeFundingRates() { |
| #212 | return imperialGet(this.config.base, "/gmtrade/funding-rates"); |
| #213 | } |
| #214 | async getPositions(wallet) { |
| #215 | const path = wallet ? `/positions?wallet=${wallet}` : `/positions?wallet=${this.config.wallet}`; |
| #216 | return imperialGet(this.config.base, path); |
| #217 | } |
| #218 | async getOrders(wallet) { |
| #219 | const path = wallet ? `/orders?wallet=${wallet}` : `/orders?wallet=${this.config.wallet}`; |
| #220 | return imperialGet(this.config.base, path); |
| #221 | } |
| #222 | async getPassthroughOrders(wallet) { |
| #223 | const w = wallet ?? this.config.wallet; |
| #224 | return imperialGet(this.config.base, `/passthrough/users/${w}/orders`); |
| #225 | } |
| #226 | async getRoute(asset, side, notional) { |
| #227 | const params = new URLSearchParams({ |
| #228 | asset, |
| #229 | side: String(side), |
| #230 | notional: String(notional), |
| #231 | }); |
| #232 | return imperialGet(this.config.base, `/route?${params}`); |
| #233 | } |
| #234 | async getPriorityFee() { |
| #235 | return imperialGet(this.config.base, "/priority-fee"); |
| #236 | } |
| #237 | async getTrades(wallet) { |
| #238 | const path = wallet ? `/trades?wallet=${wallet}` : `/trades?wallet=${this.config.wallet}`; |
| #239 | return imperialGet(this.config.base, path); |
| #240 | } |
| #241 | // ── Auth reads ── |
| #242 | async getBalances() { |
| #243 | this.requireJwt(); |
| #244 | return imperialGet(this.config.base, "/mobile/balances", this.config.jwt); |
| #245 | } |
| #246 | // ── Trading ── |
| #247 | requireJwt() { |
| #248 | if (!this.config.jwt) { |
| #249 | throw new Error("No Imperial JWT. Set IMPERIAL_JWT env var or complete connect/exchange flow."); |
| #250 | } |
| #251 | } |
| #252 | requireLive(context) { |
| #253 | if (!this.config.live) { |
| #254 | throw new Error(`${context}: live mode not enabled. Set IMPERIAL_LIVE=true to submit real orders.`); |
| #255 | } |
| #256 | } |
| #257 | /** Build a baseline order request with all required fields. */ |
| #258 | buildOrderRequest(opts) { |
| #259 | const sym = opts.symbol.toUpperCase(); |
| #260 | if (!this.config.allowedSymbols.includes(sym)) { |
| #261 | throw new Error(`${sym} is not in IMPERIAL_ALLOWED_SYMS.`); |
| #262 | } |
| #263 | const sizeFixed = usdToFixed(Math.min(opts.sizeUsd, this.config.maxSizeUsd)); |
| #264 | if (sizeFixed <= 0) |
| #265 | throw new Error("Order size must be positive."); |
| #266 | return { |
| #267 | wallet: this.config.wallet, |
| #268 | profileIndex: this.config.profileIndex, |
| #269 | action: opts.action, |
| #270 | side: opts.side, |
| #271 | underwriter: opts.underwriter ?? Underwriter.Phoenix, |
| #272 | orderType: opts.orderType ?? 0, |
| #273 | sizeUsd: sizeFixed, |
| #274 | collateralAmount: opts.collateralAmount ?? sizeFixed, |
| #275 | slippageBps: opts.slippageBps ?? this.config.slippageBps, |
| #276 | fundingStatus: opts.fundingStatus ?? 0, |
| #277 | priority: opts.priority ?? 0, |
| #278 | triggerPrice: opts.triggerPrice ?? 0, |
| #279 | triggerCondition: opts.triggerCondition ?? 0, |
| #280 | symbol: sym, |
| #281 | extraData: opts.extraData ?? null, |
| #282 | parentOrderPda: opts.parentOrderPda ?? null, |
| #283 | }; |
| #284 | } |
| #285 | /** Submit a single order. Dry-run when IMPERIAL_LIVE is not set. */ |
| #286 | async placeOrder(req, dryRun = !this.config.live) { |
| #287 | this.requireJwt(); |
| #288 | if (!dryRun) |
| #289 | this.requireLive("placeOrder"); |
| #290 | const record = { |
| #291 | id: makeId(), |
| #292 | ts: Date.now(), |
| #293 | wallet: this.config.wallet, |
| #294 | profileIndex: this.config.profileIndex, |
| #295 | venue: UNDERWRITER_LABELS[req.underwriter], |
| #296 | underwriter: req.underwriter, |
| #297 | symbol: req.symbol ?? "?", |
| #298 | side: req.side === 0 ? "long" : "short", |
| #299 | action: req.action === 0 ? "increase" : "decrease", |
| #300 | orderType: ORDER_TYPE_NAMES[req.orderType] ?? String(req.orderType), |
| #301 | sizeUsd: fixedToUsd(req.sizeUsd), |
| #302 | dryRun, |
| #303 | request: req, |
| #304 | response: null, |
| #305 | status: dryRun ? "preview" : "submitted", |
| #306 | }; |
| #307 | if (dryRun) { |
| #308 | record.response = { dry_run: true, payload: req }; |
| #309 | return { response: { success: true, error: null, orderPda: null, signature: null }, record }; |
| #310 | } |
| #311 | try { |
| #312 | const response = await imperialPost(this.config.base, "/mobile/orders", req, this.config.jwt); |
| #313 | record.response = response; |
| #314 | record.status = response.success ? "submitted" : "failed"; |
| #315 | record.error = response.error ?? undefined; |
| #316 | record.txSignature = response.signature ?? undefined; |
| #317 | record.orderPda = response.orderPda ?? undefined; |
| #318 | return { response, record }; |
| #319 | } |
| #320 | catch (err) { |
| #321 | record.status = "failed"; |
| #322 | record.error = err instanceof Error ? err.message : String(err); |
| #323 | record.response = null; |
| #324 | return { |
| #325 | response: { success: false, error: record.error, orderPda: null, signature: null }, |
| #326 | record, |
| #327 | }; |
| #328 | } |
| #329 | } |
| #330 | /** Submit entry + TP/SL legs in one atomic batch request. */ |
| #331 | async placeBatch(req, dryRun = !this.config.live) { |
| #332 | this.requireJwt(); |
| #333 | if (!dryRun) |
| #334 | this.requireLive("placeBatch"); |
| #335 | if (dryRun) { |
| #336 | const mock = { |
| #337 | entry: { success: true, error: null, orderPda: null, signature: null }, |
| #338 | closeOrders: (req.closeOrders ?? []).map(() => ({ |
| #339 | success: true, |
| #340 | error: null, |
| #341 | orderPda: null, |
| #342 | signature: null, |
| #343 | })), |
| #344 | }; |
| #345 | return { response: mock, records: [] }; |
| #346 | } |
| #347 | const response = await imperialPost(this.config.base, "/mobile/orders/batch", req, this.config.jwt); |
| #348 | const records = []; |
| #349 | const entryRecord = { |
| #350 | id: makeId(), |
| #351 | ts: Date.now(), |
| #352 | wallet: this.config.wallet, |
| #353 | profileIndex: this.config.profileIndex, |
| #354 | venue: UNDERWRITER_LABELS[req.entry.underwriter], |
| #355 | underwriter: req.entry.underwriter, |
| #356 | symbol: req.entry.symbol ?? "?", |
| #357 | side: req.entry.side === 0 ? "long" : "short", |
| #358 | action: "increase", |
| #359 | orderType: ORDER_TYPE_NAMES[req.entry.orderType] ?? String(req.entry.orderType), |
| #360 | sizeUsd: fixedToUsd(req.entry.sizeUsd), |
| #361 | dryRun: false, |
| #362 | request: req.entry, |
| #363 | response: response.entry, |
| #364 | status: response.entry.success ? "submitted" : "failed", |
| #365 | error: response.entry.error ?? undefined, |
| #366 | txSignature: response.entry.signature ?? undefined, |
| #367 | orderPda: response.entry.orderPda ?? undefined, |
| #368 | }; |
| #369 | records.push(entryRecord); |
| #370 | return { response, records }; |
| #371 | } |
| #372 | async cancelOrder(req) { |
| #373 | this.requireJwt(); |
| #374 | return imperialPost(this.config.base, "/mobile/orders/cancel", req, this.config.jwt); |
| #375 | } |
| #376 | async updateOrder(req) { |
| #377 | this.requireJwt(); |
| #378 | return imperialPost(this.config.base, "/mobile/orders/update", req, this.config.jwt); |
| #379 | } |
| #380 | async editCollateral(req) { |
| #381 | this.requireJwt(); |
| #382 | return imperialPost(this.config.base, "/mobile/orders/collateral", req, this.config.jwt); |
| #383 | } |
| #384 | async buildDepositTx(req) { |
| #385 | return imperialPost(this.config.base, "/deposit/build-tx", req); |
| #386 | } |
| #387 | async syncProfile(wallet, index) { |
| #388 | const w = wallet ?? this.config.wallet; |
| #389 | const i = index ?? this.config.profileIndex; |
| #390 | return imperialPost(this.config.base, `/passthrough/users/${w}/profiles/${i}/sync`, {}); |
| #391 | } |
| #392 | async registerPhoenix(wallet, profileIndex) { |
| #393 | return imperialPost(this.config.base, "/phoenix/register", { |
| #394 | wallet: wallet ?? this.config.wallet, |
| #395 | profileIndex: profileIndex ?? this.config.profileIndex, |
| #396 | }); |
| #397 | } |
| #398 | // ── Market snapshot + OODA ── |
| #399 | /** Fetch a full market snapshot for signal scoring. */ |
| #400 | async fetchSnapshot(symbol) { |
| #401 | const sym = symbol.toUpperCase(); |
| #402 | const [fundingRates, marks, depth] = await Promise.allSettled([ |
| #403 | this.getFundingRates(), |
| #404 | this.getMarkPrices(), |
| #405 | this.getPhoenixDepth(sym).catch(() => null), |
| #406 | ]); |
| #407 | const allFunding = fundingRates.status === "fulfilled" |
| #408 | ? asArray(fundingRates.value, ["fundingRates", "data", "items"]) |
| #409 | : []; |
| #410 | const allMarks = marks.status === "fulfilled" |
| #411 | ? asArray(marks.value, ["markPrices", "marks", "data", "items"]) |
| #412 | : []; |
| #413 | const rawDepth = depth.status === "fulfilled" ? depth.value : null; |
| #414 | const markEntry = allMarks.find((m) => m.symbol.toUpperCase() === sym && m.venue === "phoenix") ?? allMarks.find((m) => m.symbol.toUpperCase() === sym); |
| #415 | const depthSnapRaw = Array.isArray(rawDepth) |
| #416 | ? rawDepth.find((d) => d.symbol.toUpperCase() === sym) ?? null |
| #417 | : rawDepth; |
| #418 | const depthSnap = Array.isArray(depthSnapRaw?.bids) && Array.isArray(depthSnapRaw?.asks) |
| #419 | ? depthSnapRaw |
| #420 | : null; |
| #421 | return { |
| #422 | symbol: sym, |
| #423 | markPrice: markEntry?.price ?? null, |
| #424 | fundingRates: allFunding.filter((r) => r.symbol.toUpperCase() === sym), |
| #425 | depth: depthSnap, |
| #426 | }; |
| #427 | } |
| #428 | /** Full OODA cycle: observe → score → decide → optionally route. */ |
| #429 | async runCycle(symbol, opts = {}) { |
| #430 | const snap = await this.fetchSnapshot(symbol); |
| #431 | const signal = scoreImperialMarket(snap); |
| #432 | if (signal.decision === "watch" || !opts.autoRoute) { |
| #433 | return { signal, record: null }; |
| #434 | } |
| #435 | const req = this.buildOrderRequest({ |
| #436 | symbol: signal.symbol, |
| #437 | side: signal.decision === "buy" ? 0 : 1, |
| #438 | action: 0, |
| #439 | sizeUsd: opts.sizeUsd ?? this.config.maxSizeUsd, |
| #440 | }); |
| #441 | const { record } = await this.placeOrder(req, !this.config.live); |
| #442 | return { signal, record }; |
| #443 | } |
| #444 | /** Scan all allowed symbols and return ranked signals. */ |
| #445 | async runScan(opts = {}) { |
| #446 | const snaps = await Promise.all(this.config.allowedSymbols.map((sym) => this.fetchSnapshot(sym))); |
| #447 | const signals = snaps.map((s) => scoreImperialMarket(s)); |
| #448 | signals.sort((a, b) => b.confidence - a.confidence); |
| #449 | const records = []; |
| #450 | if (opts.autoRoute) { |
| #451 | for (const sig of signals.filter((s) => s.decision !== "watch")) { |
| #452 | try { |
| #453 | const req = this.buildOrderRequest({ |
| #454 | symbol: sig.symbol, |
| #455 | side: sig.decision === "buy" ? 0 : 1, |
| #456 | action: 0, |
| #457 | sizeUsd: opts.sizeUsd ?? this.config.maxSizeUsd, |
| #458 | }); |
| #459 | const { record } = await this.placeOrder(req); |
| #460 | if (record) |
| #461 | records.push(record); |
| #462 | } |
| #463 | catch { |
| #464 | // continue |
| #465 | } |
| #466 | } |
| #467 | } |
| #468 | return { signals, records }; |
| #469 | } |
| #470 | /** Health summary. */ |
| #471 | async healthCheck() { |
| #472 | const warnings = []; |
| #473 | if (!this.config.jwt) |
| #474 | warnings.push("No JWT — trading endpoints will fail."); |
| #475 | if (!this.config.wallet) |
| #476 | warnings.push("No wallet configured."); |
| #477 | if (this.config.live) |
| #478 | warnings.push("LIVE MODE — orders submit on-chain."); |
| #479 | let apiStatus = null; |
| #480 | try { |
| #481 | apiStatus = await this.getStatus(); |
| #482 | } |
| #483 | catch { |
| #484 | warnings.push("Imperial API /status unreachable."); |
| #485 | } |
| #486 | return { |
| #487 | configured: Boolean(this.config.jwt && this.config.wallet), |
| #488 | live: this.config.live, |
| #489 | wallet: this.config.wallet, |
| #490 | profileIndex: this.config.profileIndex, |
| #491 | allowedSymbols: this.config.allowedSymbols, |
| #492 | maxSizeUsd: this.config.maxSizeUsd, |
| #493 | slippageBps: this.config.slippageBps, |
| #494 | jwtPresent: Boolean(this.config.jwt), |
| #495 | apiStatus, |
| #496 | warnings, |
| #497 | }; |
| #498 | } |
| #499 | } |
| #500 | export function createImperialClient(config) { |
| #501 | return new ImperialClient(config); |
| #502 | } |
| #503 | //# sourceMappingURL=imperialAgent.js.map |