/** * Aster DEX adapter — EIP-712 agent-only signer routing (v3.3). * * Signer policy: * Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent) — REQUIRED * Tier 2 — OWS master — NOT_SUPPORTED (venue rejects master self-signing) * Tier 3 — PK direct — NOT_SUPPORTED (venue rejects master self-signing) * * Aster V3 spec requires `signer` to be a registered API_WALLET (agent). The * master wallet is never a valid signer — venue returns * `code:-1000 msg:"Signature check failed"` when user==signer==master, even * though the EIP-712 envelope is well-formed. Master/PK signers are still * accepted via setMasterSigner/setPkSigner (used during approveAgent flow), * but signed read/trade requests must use Tier 1. * * HMAC paths fully removed (see Step 3 plan — all Binance-compat HMAC code deleted). * * Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation * Base: https://fapi.asterdex.com */ import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js"; import type { EvmSigner } from "../signer/interface.js"; import type { AgentMeta } from "../settings.js"; import type { AgentSigningStrategy } from "../agent-wallet/signing-strategy.js"; type ResolvedSigner = { tier: "agent" | "master" | "pk"; signer: EvmSigner | AgentSigningStrategy; /** EVM address that will appear in the `signer` field of Aster requests */ signerAddress: string; /** EVM address of the owner/master — used as `user` field in Aster requests */ userAddress: string; }; export declare class AsterAdapter implements ExchangeAdapter { readonly name = "aster"; readonly chain = "bnb"; readonly aliases: readonly ["ast"]; private _baseUrl; private _testnet; private _agentMeta?; private _agentSigner?; private _masterSigner?; private _pkSigner?; private _useNoAgent; /** PK held between ctor and init() since LocalEvmSigner.create() is async */ private _pendingPk?; private _marketsCache; private _marketsCacheTime; private _fundingHoursCache; private _accountCache; private _positionsCache; private _ordersCache; private static readonly CACHE_TTL; private static readonly ACCOUNT_CACHE_TTL; private _nonceCounter; constructor(privateKey?: string, testnet?: boolean); init(): Promise; setAgent(meta: AgentMeta, strategy: AgentSigningStrategy): void; setMasterSigner(signer: EvmSigner): void; setPkSigner(signer: EvmSigner): void; setNoAgent(noAgent: boolean): void; get isReadOnly(): boolean; get activeSignerTier(): "agent" | "master" | "pk" | null; /** CLI symbol → Aster API symbol (ETH → ETHUSDT) */ private _toApi; /** Aster API symbol → CLI symbol (ETHUSDT → ETH) */ private _fromApi; /** Get funding interval for a symbol (lazy bootstrap). */ getFundingHours(symbol: string): Promise; getMarkets(): Promise; getOrderbook(symbol: string): Promise<{ bids: [string, string][]; asks: [string, string][]; }>; getRecentTrades(symbol: string, limit?: number): Promise; getFundingHistory(symbol: string, limit?: number): Promise<{ time: number; rate: string; price: string | null; }[]>; getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise; getBalance(): Promise; getPositions(): Promise; getOpenOrders(): Promise; getOrderHistory(limit?: number): Promise; getTradeHistory(limit?: number): Promise; getFundingPayments(limit?: number): Promise; marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: { reduceOnly?: boolean; }): Promise; limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: { reduceOnly?: boolean; tif?: string; }): Promise; editOrder(symbol: string, orderId: string, price: string, size: string): Promise; cancelOrder(symbol: string, orderId: string): Promise; cancelAllOrders(symbol?: string): Promise; setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise; stopOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, opts?: { limitPrice?: string; reduceOnly?: boolean; }): Promise; withdraw(amount: string, _destination: string, _opts?: { assetId?: number; routeType?: number; }): Promise; /** * Resolve the active signer per the three-tier policy. * * Exposed (leading underscore) for unit-test access only — do not call * from production code outside this class. */ _resolveSigner(): ResolvedSigner; private _nextNonce; /** * Build the canonical signed-request URL per Aster v3 spec. * * Reference: https://github.com/asterdex/api-docs (V3, EN, send_by_url). * The Python reference adds keys to the dict in this exact order: * nonce, user, signer * then URL-encodes the dict to form the EIP-712 `msg`. After signing, * `&signature=` is appended to the same query string. Authority verification * reconstructs `msg` from the URL query params minus `signature`, so the * signed dict and the URL query string must be byte-identical except for * the appended signature. * * Both `user` and `signer` are ALWAYS emitted as distinct query fields. * In production (Tier 1 only), `user` is the master and `signer` is the * registered agent address. Aster's authority server checks both fields. * * `signatureChainId` is NOT a v3 parameter — it's an artifact of an older * scheme; including it breaks signature verification on every call. * * Exposed (with leading underscore) for unit-test access only — do not call * from production code outside this class. */ _buildSignedQueryString(params: Record, resolved: ResolvedSigner): Promise; /** * Sign and POST to a path using EIP-712 Domain B. */ private _signedPostEip712; /** * Sign and GET from a path using EIP-712 Domain B. * * Unlike public GETs, signed GETs route through _handleAsterResponse so * venue JSON error envelopes ({code, msg}) are not silently treated as * success (SSOT Rule #2). * * Retries up to 3 attempts with exponential backoff (2s/4s/8s) on HTTP 429 * rate-limit. Each retry rebuilds a fresh signed query string (new nonce * + signature) — Aster reuses recent nonces strictly, so reusing a stale * one would itself be rejected. Non-429 venue errors throw immediately. */ private _signedGetEip712; /** * Sign and DELETE from a path using EIP-712 Domain B. * Same retry behavior as _signedGetEip712. */ private _signedDeleteEip712; /** Shared GET/DELETE retry loop with fresh-nonce per attempt. * * Total 4 attempts: initial + 3 retries with backoffs 2s, 4s, 8s. * Codex v0.12.12 final QA #3: previously MAX_ATTEMPTS = 3 truncated the * documented curve so the 8s backoff was never reachable in practice. */ private _signedRequestWithRetry; /** * Unified response validator for ALL signed Aster requests. * * Validates BOTH HTTP status AND the venue JSON error envelope. Aster's * API commonly returns HTTP 200 + `{code, msg: "Signature check failed"}` * for signing or auth failures — checking only HTTP status would silently * accept these as success and cause callers (e.g., getBalance) to cache * zero balances or empty arrays. SSOT Rule #2: failures throw with * remediation; never substitute a default. * * Used by signed POST/GET/DELETE. Public unsigned GETs use _handleResponse. */ private _handleAsterResponse; /** Handle 429 rate limit with retry — used by public unsigned GET multi-attempt loops */ private _handleResponse; private _publicGet; } export {};