perp-cli 0.14.3 → 0.16.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/README.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +126 -6
- package/dist/commands/account.js +168 -24
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +67 -62
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -0
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +160 -0
- package/dist/exchanges/risex/adapter.js +842 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +8 -8
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +19 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +15 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +131 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
package/dist/pacifica/client.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getNetworkConfig } from "./constants.js";
|
|
2
|
-
import { buildSignedRequest, buildAgentSignedRequest } from "./signing.js";
|
|
2
|
+
import { buildSignedRequest, buildAgentSignedRequest, signWithWallet } from "./signing.js";
|
|
3
3
|
import { PerpError, extractErrorMessage, orderNotSent, orderOutcomeUnknown, sendFailure } from "../errors.js";
|
|
4
4
|
import { invalidateCache } from "../cache.js";
|
|
5
|
+
import { requireSolanaAddress } from "../exchanges/address.js";
|
|
6
|
+
import { PACIFICA_COST_CANCEL, PACIFICA_COST_STANDARD, PACIFICA_MAX_THROTTLE_WAIT_MS, PACIFICA_READ_NEED, PACIFICA_SIGNED_MAX_WAIT_MS, pacificaRateLimiter, } from "./rate-limit.js";
|
|
5
7
|
/** Per-request timeout of a POST (fetch + body read). */
|
|
6
8
|
export const PACIFICA_POST_TIMEOUT_MS = 15_000;
|
|
7
9
|
/**
|
|
@@ -14,6 +16,37 @@ export const PACIFICA_POST_TIMEOUT_MS = 15_000;
|
|
|
14
16
|
const DEFINITE_4XX_PATHS = new Set(["/positions/tpsl", "/account/leverage"]);
|
|
15
17
|
/** POST endpoints that only read (signed listings): a failure there is an ordinary read error. */
|
|
16
18
|
const READ_POST_PATHS = new Set(["/account/subaccount/list", "/agent/list", "/account/api_keys"]);
|
|
19
|
+
/** Cancels cost 0.5 credits (docs: rate-limits "Order cancellation 0.5"), by path and by signed operation type. */
|
|
20
|
+
const CANCEL_POST_PATHS = new Set(["/orders/cancel", "/orders/stop/cancel", "/orders/cancel_all", "/orders/twap/cancel"]);
|
|
21
|
+
const CANCEL_OPERATIONS = new Set(["cancel_order", "cancel_stop_order", "cancel_all_orders", "cancel_twap_order"]);
|
|
22
|
+
/** Signed operation types of the READ_POST_PATHS listings: the read need applies before and after signing. */
|
|
23
|
+
const READ_OPERATIONS = new Set(["list_subaccounts", "list_agent_wallets", "list_api_keys"]);
|
|
24
|
+
/**
|
|
25
|
+
* A signed write needs only its own cost (never the read reserve); `maxWaitMs` = how long it may
|
|
26
|
+
* wait at this point (before signing: the normal bound; after signing: inside expiry_window).
|
|
27
|
+
*/
|
|
28
|
+
function writeNeed(cancel, maxWaitMs) {
|
|
29
|
+
const cost = cancel ? PACIFICA_COST_CANCEL : PACIFICA_COST_STANDARD;
|
|
30
|
+
return { cost, reserve: 0, maxWaitMs };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Credits ×10 reserved for one POST /orders/batch. The docs (api/rate-limits "Credit Costs") price
|
|
34
|
+
* a "Standard request/action" at 1 credit and an "Order cancellation" at 0.5 and say nothing
|
|
35
|
+
* batch-specific (batch-order page: no cost); every action is individually signed and processed,
|
|
36
|
+
* so the batch is reserved per ACTION — the conservative reading (over-reserving only throttles
|
|
37
|
+
* earlier; the venue's ratelimit headers on the response settle the real count).
|
|
38
|
+
*/
|
|
39
|
+
export function pacificaBatchCost(types) {
|
|
40
|
+
return types.reduce((a, t) => a + (t === "Cancel" || t === "CancelStopOrder" ? PACIFICA_COST_CANCEL : PACIFICA_COST_STANDARD), 0);
|
|
41
|
+
}
|
|
42
|
+
/** Query parameters that carry a Solana address (account reads, Print orders, vaults). */
|
|
43
|
+
const ADDRESS_PARAMS = new Set(["account", "game_account", "lake"]);
|
|
44
|
+
export { requireSolanaAddress };
|
|
45
|
+
function checkAddressParams(params) {
|
|
46
|
+
for (const [k, v] of Object.entries(params ?? {}))
|
|
47
|
+
if (ADDRESS_PARAMS.has(k) && v !== undefined && v !== null)
|
|
48
|
+
requireSolanaAddress(v, k);
|
|
49
|
+
}
|
|
17
50
|
export class PacificaClient {
|
|
18
51
|
baseUrl;
|
|
19
52
|
apiKey;
|
|
@@ -24,12 +57,21 @@ export class PacificaClient {
|
|
|
24
57
|
* against the agent key. Undefined for master/PK signers.
|
|
25
58
|
*/
|
|
26
59
|
requestAgentWallet;
|
|
60
|
+
/** Credit-quota state for this base URL + key tier (rate-limit.ts; process-local): unsigned GETs (IP quota) … */
|
|
61
|
+
rate;
|
|
62
|
+
/** … and signed POSTs (may report the account's quota) kept apart. */
|
|
63
|
+
signedRate;
|
|
27
64
|
/** Attach (or clear) the agent wallet used for subsequent signed requests. */
|
|
28
65
|
setRequestAgentWallet(agentWallet) {
|
|
29
66
|
this.requestAgentWallet = agentWallet;
|
|
30
67
|
}
|
|
31
68
|
/** Build a signed POST body, attaching agent_wallet when an agent is the active signer. */
|
|
32
69
|
async signedBody(operationType, payload, account, signMessage) {
|
|
70
|
+
// Throttle BEFORE signing: a wait after signing would eat into the body's expiry_window.
|
|
71
|
+
// A fail-fast RATE_LIMITED here sent nothing (details.sent === false).
|
|
72
|
+
await this.signedRate.awaitCapacity(operationType, READ_OPERATIONS.has(operationType)
|
|
73
|
+
? PACIFICA_READ_NEED
|
|
74
|
+
: writeNeed(CANCEL_OPERATIONS.has(operationType), PACIFICA_MAX_THROTTLE_WAIT_MS));
|
|
33
75
|
try {
|
|
34
76
|
return await (this.requestAgentWallet
|
|
35
77
|
? buildAgentSignedRequest(operationType, payload, account, this.requestAgentWallet, signMessage)
|
|
@@ -44,6 +86,8 @@ export class PacificaClient {
|
|
|
44
86
|
this.baseUrl =
|
|
45
87
|
config.baseUrl || getNetworkConfig(config.network).restUrl;
|
|
46
88
|
this.apiKey = config.apiKey;
|
|
89
|
+
this.rate = pacificaRateLimiter(this.baseUrl, this.apiKey, "unsigned");
|
|
90
|
+
this.signedRate = pacificaRateLimiter(this.baseUrl, this.apiKey, "signed");
|
|
47
91
|
}
|
|
48
92
|
headers() {
|
|
49
93
|
const h = { "Content-Type": "application/json" };
|
|
@@ -52,6 +96,7 @@ export class PacificaClient {
|
|
|
52
96
|
return h;
|
|
53
97
|
}
|
|
54
98
|
async get(path, params) {
|
|
99
|
+
checkAddressParams(params);
|
|
55
100
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
56
101
|
if (params) {
|
|
57
102
|
Object.entries(params).forEach(([k, v]) => {
|
|
@@ -59,9 +104,20 @@ export class PacificaClient {
|
|
|
59
104
|
url.searchParams.set(k, v);
|
|
60
105
|
});
|
|
61
106
|
}
|
|
62
|
-
const
|
|
107
|
+
const ticket = await this.rate.acquire(`GET ${path}`, PACIFICA_READ_NEED);
|
|
108
|
+
let res;
|
|
109
|
+
try {
|
|
110
|
+
res = await fetch(url.toString(), { headers: this.headers() });
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
ticket.release();
|
|
114
|
+
}
|
|
115
|
+
this.rate.observe(res);
|
|
63
116
|
if (!res.ok) {
|
|
64
117
|
const text = await res.text();
|
|
118
|
+
// Structural (by status): RATE_LIMITED with retryAfterMs; never retried here.
|
|
119
|
+
if (res.status === 429)
|
|
120
|
+
throw this.rate.rateLimitedError(`GET ${path}`, res, text);
|
|
65
121
|
throw new Error(`GET ${path} failed (${res.status}): ${text}`);
|
|
66
122
|
}
|
|
67
123
|
const json = await res.json();
|
|
@@ -77,14 +133,21 @@ export class PacificaClient {
|
|
|
77
133
|
* outcome unknown → ORDER_PENDING (sendFailure), never a retryable network error. No connection
|
|
78
134
|
* at all → not sent. A 4xx is the venue's refusal and keeps the plain `POST <path> failed
|
|
79
135
|
* (<status>): <body>` error the adapters map (422 business codes) — except on
|
|
80
|
-
* DEFINITE_4XX_PATHS, where it is a definite ACTION_REJECTED
|
|
81
|
-
*
|
|
136
|
+
* DEFINITE_4XX_PATHS, where it is a definite ACTION_REJECTED. HTTP 429 → RATE_LIMITED
|
|
137
|
+
* (details.sent === false, retryAfterMs) — never retried, a write is never resubmitted.
|
|
138
|
+
* "429 = not executed" is our reading of the docs ("When credits are exhausted, requests return
|
|
139
|
+
* HTTP 429"), not an explicit Pacifica statement. Before sending, the credit throttle may wait up
|
|
140
|
+
* to PACIFICA_SIGNED_MAX_WAIT_MS (the body is already signed) or fail fast with RATE_LIMITED
|
|
141
|
+
* (nothing sent). A write needs only its own cost, never the read reserve. A fetch timeout (PACIFICA_POST_TIMEOUT_MS) on a write is ORDER_PENDING too;
|
|
82
142
|
* on a listing read a timeout is TIMEOUT and any other transport failure EXCHANGE_UNREACHABLE.
|
|
83
143
|
*/
|
|
84
|
-
async post(path, body) {
|
|
144
|
+
async post(path, body, needOverride) {
|
|
85
145
|
const write = !READ_POST_PATHS.has(path);
|
|
86
146
|
const what = `POST ${path}`;
|
|
87
147
|
const remediation = `Do NOT resubmit — the request may have been executed. Check open orders, positions and balances first: perp -e pacifica account orders / portfolio`;
|
|
148
|
+
const ticket = await this.signedRate.acquire(what, needOverride !== undefined ? needOverride : write
|
|
149
|
+
? writeNeed(CANCEL_POST_PATHS.has(path), PACIFICA_SIGNED_MAX_WAIT_MS)
|
|
150
|
+
: { ...PACIFICA_READ_NEED, maxWaitMs: PACIFICA_SIGNED_MAX_WAIT_MS });
|
|
88
151
|
try {
|
|
89
152
|
let res;
|
|
90
153
|
let text;
|
|
@@ -95,6 +158,7 @@ export class PacificaClient {
|
|
|
95
158
|
body: JSON.stringify(body),
|
|
96
159
|
signal: AbortSignal.timeout(PACIFICA_POST_TIMEOUT_MS),
|
|
97
160
|
});
|
|
161
|
+
ticket.settle(res);
|
|
98
162
|
text = await res.text();
|
|
99
163
|
}
|
|
100
164
|
catch (e) {
|
|
@@ -110,8 +174,11 @@ export class PacificaClient {
|
|
|
110
174
|
if (write && res.status >= 500) {
|
|
111
175
|
throw orderOutcomeUnknown("pacifica", what, new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`), remediation);
|
|
112
176
|
}
|
|
177
|
+
// Refused before processing (credits exhausted): nothing executed, never resubmitted here.
|
|
178
|
+
if (res.status === 429)
|
|
179
|
+
throw this.signedRate.rateLimitedError(what, res, text);
|
|
113
180
|
if (!res.ok) {
|
|
114
|
-
if (DEFINITE_4XX_PATHS.has(path) && res.status >= 400 && res.status < 500
|
|
181
|
+
if (DEFINITE_4XX_PATHS.has(path) && res.status >= 400 && res.status < 500) {
|
|
115
182
|
throw new PerpError("ACTION_REJECTED", `Pacifica ${what} refused (HTTP ${res.status}, nothing was changed): ${text.slice(0, 300)}`, {
|
|
116
183
|
exchange: "pacifica",
|
|
117
184
|
httpStatus: res.status,
|
|
@@ -130,6 +197,7 @@ export class PacificaClient {
|
|
|
130
197
|
}
|
|
131
198
|
}
|
|
132
199
|
finally {
|
|
200
|
+
ticket.release(); // no-op once settled; a transport failure frees the reservation
|
|
133
201
|
// Every POST is an account write (order / cancel / TP/SL / leverage / transfer): drop the
|
|
134
202
|
// shared acct: disk cache so no later read serves the pre-write positions / orders.
|
|
135
203
|
invalidateCache("acct");
|
|
@@ -377,6 +445,71 @@ export class PacificaClient {
|
|
|
377
445
|
const body = await this.signedBody("withdraw_from_lake", params, account, signMessage);
|
|
378
446
|
return this.post("/lake/withdraw", body);
|
|
379
447
|
}
|
|
448
|
+
// ==================== PRINT (game) + VAULT READS / PRE-SIGNED WRITES ====================
|
|
449
|
+
// Shapes and parsing live in src/exchanges/pacifica-print.ts / pacifica-vaults.ts.
|
|
450
|
+
/**
|
|
451
|
+
* Unauthenticated GET that keeps the whole {success, data, has_more, next_cursor} envelope.
|
|
452
|
+
* Same credit limiter as get() (unsigned IP quota, PACIFICA_READ_NEED: may wait for the window,
|
|
453
|
+
* else RATE_LIMITED sent:false); the response headers are recorded; HTTP 429 → RATE_LIMITED
|
|
454
|
+
* (structural, never retried). Timeout → TIMEOUT, transport failure → EXCHANGE_UNREACHABLE,
|
|
455
|
+
* non-JSON / non-object body → EXCHANGE_ERROR (a read: nothing can have changed).
|
|
456
|
+
*/
|
|
457
|
+
async getEnvelope(path, params = {}) {
|
|
458
|
+
checkAddressParams(params);
|
|
459
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
460
|
+
for (const [k, v] of Object.entries(params))
|
|
461
|
+
url.searchParams.set(k, v);
|
|
462
|
+
const what = `GET ${path}`;
|
|
463
|
+
const ticket = await this.rate.acquire(what, PACIFICA_READ_NEED);
|
|
464
|
+
let res;
|
|
465
|
+
let text;
|
|
466
|
+
try {
|
|
467
|
+
res = await fetch(url.toString(), { headers: this.headers(), signal: AbortSignal.timeout(PACIFICA_POST_TIMEOUT_MS) });
|
|
468
|
+
ticket.settle(res);
|
|
469
|
+
text = await res.text();
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
const timedOut = e instanceof Error && (e.name === "TimeoutError" || e.name === "AbortError");
|
|
473
|
+
throw new PerpError(timedOut ? "TIMEOUT" : "EXCHANGE_UNREACHABLE", `Pacifica GET ${path} ${timedOut ? `timed out after ${PACIFICA_POST_TIMEOUT_MS} ms` : `failed: ${extractErrorMessage(e)}`}`, {
|
|
474
|
+
exchange: "pacifica", remediation: "Retry; if it keeps failing check the network and Pacifica's status",
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
finally {
|
|
478
|
+
ticket.release(); // no-op once settled; a transport failure frees the reservation
|
|
479
|
+
}
|
|
480
|
+
if (res.status === 429)
|
|
481
|
+
throw this.rate.rateLimitedError(what, res, text);
|
|
482
|
+
if (!res.ok)
|
|
483
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text}`);
|
|
484
|
+
let json;
|
|
485
|
+
try {
|
|
486
|
+
json = JSON.parse(text);
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica GET ${path} returned a non-JSON body (HTTP ${res.status}): ${text.slice(0, 160)}`, { exchange: "pacifica" });
|
|
490
|
+
}
|
|
491
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
492
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica GET ${path} returned a non-object body: ${text.slice(0, 160)}`, { exchange: "pacifica" });
|
|
493
|
+
}
|
|
494
|
+
return json;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* POST a body the caller already built and signed (Print / vault writes sign their own canonical
|
|
498
|
+
* message so `--dry-run` can show the exact body). Same unknown-outcome rules as every write.
|
|
499
|
+
*/
|
|
500
|
+
async postPresigned(path, body) {
|
|
501
|
+
return this.post(path, body);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Pre-sign credit wait for a pre-signed write (same rule as signedBody / amend): call BEFORE
|
|
505
|
+
* building + signing, so the post-sign wait in post() (≤ PACIFICA_SIGNED_MAX_WAIT_MS, inside the
|
|
506
|
+
* 5 s expiry_window) finds credits. Cost: the docs' rate-limits table lists no Print / vault
|
|
507
|
+
* entry — "Standard request/action: 1 credit" applies (end_game is not an order cancellation).
|
|
508
|
+
* Fail-fast RATE_LIMITED here sent and signed nothing (details.sent === false).
|
|
509
|
+
*/
|
|
510
|
+
async awaitPresignedCapacity(operationType) {
|
|
511
|
+
await this.signedRate.awaitCapacity(operationType, writeNeed(false, PACIFICA_MAX_THROTTLE_WAIT_MS));
|
|
512
|
+
}
|
|
380
513
|
// ==================== BUILDER CODE ====================
|
|
381
514
|
async approveBuilderCode(params, account, signMessage) {
|
|
382
515
|
const body = await this.signedBody("approve_builder_code", params, account, signMessage);
|
|
@@ -408,7 +541,47 @@ export class PacificaClient {
|
|
|
408
541
|
return this.post("/referral/user/code/claim", body);
|
|
409
542
|
}
|
|
410
543
|
// ==================== BATCH ====================
|
|
411
|
-
|
|
412
|
-
|
|
544
|
+
/**
|
|
545
|
+
* POST /orders/batch with the per-action cost (pacificaBatchCost) reserved. `maxWaitMs` caps the
|
|
546
|
+
* post-sign throttle wait (default PACIFICA_SIGNED_MAX_WAIT_MS, never more): a caller with a
|
|
547
|
+
* tighter signature deadline passes what is left of it, so a wait can never send an expired batch
|
|
548
|
+
* — a longer wait fails fast with RATE_LIMITED (sent: false, nothing sent).
|
|
549
|
+
*/
|
|
550
|
+
async batchOrders(actions, opts = {}) {
|
|
551
|
+
const maxWaitMs = Math.max(0, Math.min(opts.maxWaitMs !== undefined ? opts.maxWaitMs : PACIFICA_SIGNED_MAX_WAIT_MS, PACIFICA_SIGNED_MAX_WAIT_MS));
|
|
552
|
+
return this.post("/orders/batch", { actions }, { cost: pacificaBatchCost(actions.map(a => a.type)), reserve: 0, maxWaitMs });
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Pre-sign throttle for a batch (like signedBody does for single writes): wait — up to
|
|
556
|
+
* PACIFICA_MAX_THROTTLE_WAIT_MS, else RATE_LIMITED with nothing signed or sent — until the
|
|
557
|
+
* batch's whole cost fits, so the post-sign acquire in batchOrders normally finds the credits.
|
|
558
|
+
*/
|
|
559
|
+
async awaitBatchCapacity(types) {
|
|
560
|
+
await this.signedRate.awaitCapacity("POST /orders/batch", { cost: pacificaBatchCost(types), reserve: 0, maxWaitMs: PACIFICA_MAX_THROTTLE_WAIT_MS });
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* One signed batch action body (docs rest-api/orders/batch-order: "Batch orders are not signed
|
|
564
|
+
* as a whole, but rather by its individual actions components"; each action's `data` carries the
|
|
565
|
+
* fields of its standalone endpoint, signed with that endpoint's operation type). Nothing is sent.
|
|
566
|
+
* `header` fixes the signed timestamp / expiry window, so every action of one batch can share
|
|
567
|
+
* one timestamp. agent_wallet is attached when an agent is the active signer. A signing failure
|
|
568
|
+
* → ORDER_REJECTED (orderNotSent).
|
|
569
|
+
*/
|
|
570
|
+
async signBatchAction(operationType, payload, account, signMessage, header) {
|
|
571
|
+
const full = { type: operationType, timestamp: header.timestamp, expiry_window: header.expiry_window };
|
|
572
|
+
try {
|
|
573
|
+
const [, signature] = await signWithWallet(full, payload, signMessage);
|
|
574
|
+
return {
|
|
575
|
+
account,
|
|
576
|
+
...(this.requestAgentWallet ? { agent_wallet: this.requestAgentWallet } : {}),
|
|
577
|
+
signature,
|
|
578
|
+
timestamp: full.timestamp,
|
|
579
|
+
expiry_window: full.expiry_window,
|
|
580
|
+
...payload,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
throw orderNotSent("pacifica", operationType, e);
|
|
585
|
+
}
|
|
413
586
|
}
|
|
414
587
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica credit rate limit, read from the venue's own response headers.
|
|
3
|
+
*
|
|
4
|
+
* docs.pacifica.fi api-documentation/api/rate-limits (read 2026-10-19): credit-based, rolling
|
|
5
|
+
* 60 s window; 100 credits / 60 s per unidentified IP, 300 with a valid API Config Key (more for
|
|
6
|
+
* higher fee tiers); standard request 1, cancel 0.5, heavy GET 3-12 (1-4 with a key). Exhausted →
|
|
7
|
+
* HTTP 429. "All credit values are multiplied by 10 to support fractional costs":
|
|
8
|
+
*
|
|
9
|
+
* ratelimit: "credits";r=1200;t=32 r = remaining (×10), t = seconds until refresh
|
|
10
|
+
* ratelimit-policy: "credits";q=1250;w=60 q = quota per window (×10), w = window seconds
|
|
11
|
+
*
|
|
12
|
+
* Live mainnet GET /info/prices (2026-10-19, no key): `ratelimit: "credits";r=980;t=22`,
|
|
13
|
+
* `ratelimit-policy: "credits";q=1000;w=60`. Values below stay in those ×10 units.
|
|
14
|
+
*
|
|
15
|
+
* CloudFront caches some GETs (/info: `x-cache: Hit from cloudfront`, `age: 16`): a cache hit
|
|
16
|
+
* replays the headers of whoever filled the cache (live: q=3000, a keyed caller) — not this
|
|
17
|
+
* caller's quota, so a hit carries no information.
|
|
18
|
+
*
|
|
19
|
+
* Scope: one state per REST base URL (mainnet / testnet), credential tier (no key = IP quota;
|
|
20
|
+
* an API Config Key has its own) and request kind (signed POST vs unsigned GET). Process-local: other perp-cli processes (CLI + MCP + bots) on the
|
|
21
|
+
* same IP spend the same quota unseen, so a 429 can still happen — it is then surfaced as
|
|
22
|
+
* RATE_LIMITED, never retried here.
|
|
23
|
+
*/
|
|
24
|
+
import { PerpError } from "../errors.js";
|
|
25
|
+
/** One parsed `ratelimit` (+ optional `ratelimit-policy`) observation, in credits ×10. */
|
|
26
|
+
export interface PacificaRateLimitInfo {
|
|
27
|
+
remaining: number;
|
|
28
|
+
resetSec: number;
|
|
29
|
+
/** null = `ratelimit-policy` absent or malformed. */
|
|
30
|
+
quota: number | null;
|
|
31
|
+
windowSec: number | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Strict parse of the `ratelimit` / `ratelimit-policy` header values. `ratelimit` absent or
|
|
35
|
+
* malformed (no `"credits"` item, missing / non-integer / duplicate r or t) → null: no info, never
|
|
36
|
+
* a guessed value. A malformed policy only nulls quota / windowSec.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parsePacificaRateLimit(ratelimit: string | null, policy: string | null): PacificaRateLimitInfo | null;
|
|
39
|
+
/** `Retry-After` in delta-seconds → ms; absent / HTTP-date / malformed → null. */
|
|
40
|
+
export declare function parseRetryAfterMs(raw: string | null): number | null;
|
|
41
|
+
type HeaderSource = {
|
|
42
|
+
headers?: {
|
|
43
|
+
get(name: string): string | null;
|
|
44
|
+
};
|
|
45
|
+
} | null | undefined;
|
|
46
|
+
/** Parsed rate-limit info of a response this caller's request produced; null = no info. */
|
|
47
|
+
export declare function pacificaRateLimitOf(res: HeaderSource): PacificaRateLimitInfo | null;
|
|
48
|
+
/** Read reserve (credits ×10): the largest documented heavy-GET cost (12 credits) stays unspent after a read. */
|
|
49
|
+
export declare const PACIFICA_READ_RESERVE = 120;
|
|
50
|
+
/** Cost (credits ×10) of a standard request / action (docs: 1 credit). */
|
|
51
|
+
export declare const PACIFICA_COST_STANDARD = 10;
|
|
52
|
+
/** Cost (credits ×10) of an order cancellation (docs: 0.5 credits). */
|
|
53
|
+
export declare const PACIFICA_COST_CANCEL = 5;
|
|
54
|
+
/** Longest pre-send wait before failing fast with RATE_LIMITED. */
|
|
55
|
+
export declare const PACIFICA_MAX_THROTTLE_WAIT_MS = 15000;
|
|
56
|
+
/** Longest wait between signing and sending (signed bodies expire after expiry_window = 5 s). */
|
|
57
|
+
export declare const PACIFICA_SIGNED_MAX_WAIT_MS = 2000;
|
|
58
|
+
export interface RateClock {
|
|
59
|
+
now(): number;
|
|
60
|
+
sleep(ms: number): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** What a request needs before it may be sent. */
|
|
63
|
+
export interface RateNeed {
|
|
64
|
+
/** Credits ×10 this request is assumed to cost (PACIFICA_COST_*). */
|
|
65
|
+
cost: number;
|
|
66
|
+
/**
|
|
67
|
+
* Credits ×10 that must still be left after this request (observed remaining − in flight − cost):
|
|
68
|
+
* reads keep the heavy-GET reserve (PACIFICA_READ_RESERVE); writes 0 — a write needs only its own
|
|
69
|
+
* cost, so an amend's replacement leg is never blocked by the read reserve.
|
|
70
|
+
*/
|
|
71
|
+
reserve: number;
|
|
72
|
+
/** Longest pre-send wait; longer → RATE_LIMITED fail-fast (0 = never wait). */
|
|
73
|
+
maxWaitMs: number;
|
|
74
|
+
}
|
|
75
|
+
export declare const PACIFICA_READ_NEED: RateNeed;
|
|
76
|
+
/** A reserved request: settle with its response (or release on a transport failure). Idempotent. */
|
|
77
|
+
export interface RateTicket {
|
|
78
|
+
settle(res: HeaderSource): PacificaRateLimitInfo | null;
|
|
79
|
+
release(): void;
|
|
80
|
+
}
|
|
81
|
+
export declare class PacificaRateLimiter {
|
|
82
|
+
private readonly label;
|
|
83
|
+
private readonly clock;
|
|
84
|
+
private obs;
|
|
85
|
+
/** Credits ×10 reserved by requests sent and not yet settled — not reset by observe(). */
|
|
86
|
+
private inFlight;
|
|
87
|
+
private lastRaw;
|
|
88
|
+
private skew;
|
|
89
|
+
constructor(label: string, clock?: RateClock);
|
|
90
|
+
/** clock.now() made monotonic: a backwards step is absorbed instead of restarting elapsed time. */
|
|
91
|
+
private now;
|
|
92
|
+
/** ms until the observed window refreshes; null = no live observation. */
|
|
93
|
+
private resetWaitMs;
|
|
94
|
+
/** Current state for diagnostics (credits ×10); null = no live observation. */
|
|
95
|
+
snapshot(): {
|
|
96
|
+
remaining: number;
|
|
97
|
+
inFlight: number;
|
|
98
|
+
effectiveRemaining: number;
|
|
99
|
+
resetInMs: number;
|
|
100
|
+
} | null;
|
|
101
|
+
/** ms to wait before a request with `need` may be sent: 0 = now (no info, or enough credits left). */
|
|
102
|
+
waitMs(need: Pick<RateNeed, "cost" | "reserve">): number;
|
|
103
|
+
private ticket;
|
|
104
|
+
/**
|
|
105
|
+
* Pre-send throttle + reservation. Enough credits (or no info) → the cost is reserved
|
|
106
|
+
* synchronously, before any await, so concurrent callers see each other. Otherwise waits for the
|
|
107
|
+
* window to refresh (total ≤ need.maxWaitMs) or fails fast with RATE_LIMITED (sent: false).
|
|
108
|
+
*/
|
|
109
|
+
acquire(what: string, need: RateNeed): Promise<RateTicket>;
|
|
110
|
+
/** Like acquire but never waits or refuses (health probe): reserves and records only. */
|
|
111
|
+
track(cost: number): RateTicket;
|
|
112
|
+
/** Wait (no reservation) until `need` would pass — used before signing, so the post-sign acquire finds credits. */
|
|
113
|
+
awaitCapacity(what: string, need: RateNeed): Promise<void>;
|
|
114
|
+
private throttled;
|
|
115
|
+
/**
|
|
116
|
+
* Record a response's headers (any status). No / malformed / cached headers → unchanged. Within
|
|
117
|
+
* the live window an observation with a higher `r` (a late response from before others landed)
|
|
118
|
+
* or a different `q` (another quota: account vs IP) is dropped.
|
|
119
|
+
*/
|
|
120
|
+
observe(res: HeaderSource): PacificaRateLimitInfo | null;
|
|
121
|
+
/**
|
|
122
|
+
* HTTP 429 → RATE_LIMITED (structural, by status). retryAfterMs = `Retry-After`, else the
|
|
123
|
+
* ratelimit reset `t`, else absent (clamped to the window). The quota is recorded as exhausted.
|
|
124
|
+
*
|
|
125
|
+
* `sent: false` ("not executed") is OUR reading of the docs — "When credits are exhausted,
|
|
126
|
+
* requests return HTTP 429" — i.e. the request is refused before processing. Pacifica does not
|
|
127
|
+
* state explicitly that a 429'd write was not executed.
|
|
128
|
+
*/
|
|
129
|
+
rateLimitedError(what: string, res: HeaderSource & {
|
|
130
|
+
status: number;
|
|
131
|
+
}, body: string): PerpError;
|
|
132
|
+
}
|
|
133
|
+
/** A Pacifica RATE_LIMITED that sent nothing (pre-send throttle or HTTP 429): a definite refusal. */
|
|
134
|
+
export declare function isPacificaRateLimited(e: unknown): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* The process-local limiter for a REST base URL + credential tier (no key = IP quota) + request
|
|
137
|
+
* kind. Signed POSTs are kept apart from unsigned GETs: credits are shared per main account and
|
|
138
|
+
* scale with its fee tier, so signed responses can report an account quota (e.g. q=3000) while
|
|
139
|
+
* keyless GETs report the IP quota (q=1000).
|
|
140
|
+
*/
|
|
141
|
+
export declare function pacificaRateLimiter(baseUrl: string, apiKey: string | undefined, kind: "signed" | "unsigned"): PacificaRateLimiter;
|
|
142
|
+
/** Test hook: forget every limiter's state. */
|
|
143
|
+
export declare function resetPacificaRateLimiters(): void;
|
|
144
|
+
export {};
|