perp-cli 0.14.2 → 0.15.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.md +152 -13
- 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/pacifica.js +83 -13
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- 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/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +9 -9
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
- package/dist/bot/strategies/funding-arb-utils.js +43 -24
- 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.js +23 -12
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +1 -1
- 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 +125 -5
- 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 +4 -4
- package/dist/commands/arb-auto.js +21 -17
- package/dist/commands/arb.d.ts +12 -0
- package/dist/commands/arb.js +61 -143
- 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.js +168 -36
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +134 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +468 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.js +32 -47
- 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 +63 -58
- 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/dashboard/server.js +1 -1
- package/dist/dashboard/ui.js +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.js +25 -3
- package/dist/exchanges/arcus/adapter.d.ts +7 -7
- package/dist/exchanges/arcus/adapter.js +12 -12
- 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/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 +9 -4
- package/dist/exchanges/aster.js +125 -55
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +1 -2
- package/dist/exchanges/canonical-symbols.js +26 -2
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +68 -0
- package/dist/exchanges/derive/options.js +360 -0
- package/dist/exchanges/extended/adapter.d.ts +82 -0
- package/dist/exchanges/extended/adapter.js +179 -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 +130 -0
- package/dist/exchanges/extended/public.js +429 -0
- package/dist/exchanges/grvt/adapter.d.ts +86 -0
- package/dist/exchanges/grvt/adapter.js +206 -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-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +5 -5
- package/dist/exchanges/hyperliquid.d.ts +22 -16
- package/dist/exchanges/hyperliquid.js +80 -55
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +97 -8
- package/dist/exchanges/interface.js +19 -2
- 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 +2 -2
- package/dist/exchanges/lighter-public.js +2 -2
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +3 -4
- package/dist/exchanges/lighter-spot.js +6 -7
- package/dist/exchanges/lighter.d.ts +11 -11
- package/dist/exchanges/lighter.js +28 -14
- package/dist/exchanges/ondo/adapter.d.ts +5 -6
- package/dist/exchanges/ondo/adapter.js +16 -14
- 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 +8 -1
- package/dist/exchanges/ondo/public.js +13 -2
- 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/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 +2 -2
- package/dist/exchanges/pacifica-spot.js +3 -3
- 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 +121 -0
- package/dist/exchanges/pacifica-vaults.js +264 -0
- package/dist/exchanges/pacifica.d.ts +32 -5
- package/dist/exchanges/pacifica.js +114 -48
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/qfex/adapter.d.ts +18 -5
- package/dist/exchanges/qfex/adapter.js +32 -12
- 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 +3 -3
- package/dist/exchanges/qfex/public.js +3 -3
- 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 +2 -2
- 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 +148 -0
- package/dist/exchanges/risex/adapter.js +796 -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 +7 -7
- 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 +54 -0
- package/dist/exchanges/txflow/adapter.js +118 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +153 -0
- package/dist/exchanges/txflow/public.d.ts +76 -0
- package/dist/exchanges/txflow/public.js +284 -0
- package/dist/exchanges/unknown-symbol.d.ts +29 -0
- package/dist/exchanges/unknown-symbol.js +51 -0
- package/dist/exchanges/variational/adapter.d.ts +71 -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 +6 -7
- package/dist/exchanges/venue-types.js +2 -2
- package/dist/exchanges/venue.d.ts +51 -8
- package/dist/exchanges/venue.js +54 -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 +10 -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 +7 -5
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +5 -3
- 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 +6 -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 +116 -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 +1 -1
- package/dist/funding/legacy-public.js +9 -4
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +44 -4
- package/dist/funding/rates.js +56 -12
- 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/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +58 -0
- package/dist/mcp-server.js +113 -59
- package/dist/pacifica/client.d.ts +67 -4
- package/dist/pacifica/client.js +170 -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/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 +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/dist/trade-validator.js +9 -4
- package/dist/utils.d.ts +12 -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/index.d.ts +959 -0
- package/dist/ws/index.js +4295 -0
- package/dist/ws/node.d.ts +975 -0
- package/dist/ws/node.js +4366 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +58 -10
- package/skills/perp-cli/SKILL.md +62 -9
- package/skills/perp-cli/references/agent-operations.md +7 -0
- package/skills/perp-cli/references/commands.md +76 -5
- package/skills/perp-cli/references/ws-library.md +492 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica vaults ("lakes" in the API) — docs.pacifica.fi/vaults.md and pages under it,
|
|
3
|
+
* api-documentation/api/rest-api/vaults/*.
|
|
4
|
+
*
|
|
5
|
+
* Product: a vault is a real Pacifica account traded by its manager; depositors (LPs) get LP
|
|
6
|
+
* shares at NAV (lp_balance ÷ lp_shares, 1:1 when the LP class is empty). PnL is attributed to the
|
|
7
|
+
* LP / manager classes before every deposit or withdrawal; the manager takes `manager_profit_share`
|
|
8
|
+
* of profit above the high-water mark. Minimum deposit 10 USDC; `deposit_cap` caps the LP balance
|
|
9
|
+
* (manager deposits are not counted). A withdrawal redeems shares (amount = shares × NAV) straight
|
|
10
|
+
* into the depositor's own trading balance — the API has no destination field — and is allowed
|
|
11
|
+
* only when: enough shares; `deposit_min_duration_ms` elapsed since the depositor's LAST deposit
|
|
12
|
+
* (each deposit resets the lock for the whole balance); inside the withdrawal window when
|
|
13
|
+
* `withdraw_window_s` / `withdraw_duration_s` are set (open while now_s mod window < duration,
|
|
14
|
+
* epoch-anchored); and the vault has free balance (a fully deployed vault may pay partially).
|
|
15
|
+
* The venue publishes no APY: `apy` is null.
|
|
16
|
+
*
|
|
17
|
+
* Endpoints (GET unauthenticated; writes signed):
|
|
18
|
+
* GET /lake/list[?account=] vaults (+ the account's `user_share`)
|
|
19
|
+
* GET /lake/get_deposit_whitelist?lake= deposit-address whitelist (app.pacifica.fi; not in the API docs)
|
|
20
|
+
* GET /account?account=<vault> · /positions the vault account's live equity / positions
|
|
21
|
+
* POST /lake/deposit type deposit_to_lake {lake, amount, idempotency_key?}
|
|
22
|
+
* POST /lake/withdraw type withdraw_from_lake {lake, shares, idempotency_key?}
|
|
23
|
+
* Live 2026-10-19: absent config fields are omitted (→ null here), and `user_share` was null for a
|
|
24
|
+
* vault's own manager (it counts LP shares; the manager's are `manager_shares`).
|
|
25
|
+
*/
|
|
26
|
+
import { PerpError } from "../errors.js";
|
|
27
|
+
import { mulDecimal, subDecimal } from "./precision.js";
|
|
28
|
+
import { cmpDecimal, divDecimal, isPlainDecimal, trimDecimal } from "./pacifica-decimal.js";
|
|
29
|
+
import { envelopeData, requireUsdcAmount, PLAIN_POSITIVE } from "./pacifica-print.js";
|
|
30
|
+
import { buildPresigned } from "./pacifica-presigned.js";
|
|
31
|
+
/** docs vaults/depositing: "Minimum deposit — Constant 10 USDC." */
|
|
32
|
+
export const VAULT_MIN_DEPOSIT_USDC = "10";
|
|
33
|
+
function bad(what, row) {
|
|
34
|
+
return new PerpError("EXCHANGE_ERROR", `Pacifica ${what}: ${JSON.stringify(row).slice(0, 200)}`, { exchange: "pacifica" });
|
|
35
|
+
}
|
|
36
|
+
function str(r, k) {
|
|
37
|
+
const v = r[k];
|
|
38
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
39
|
+
}
|
|
40
|
+
function dec(r, k) {
|
|
41
|
+
const v = r[k];
|
|
42
|
+
return isPlainDecimal(v) ? trimDecimal(v) : null;
|
|
43
|
+
}
|
|
44
|
+
function reqDec(r, k) {
|
|
45
|
+
const v = dec(r, k);
|
|
46
|
+
if (v === null)
|
|
47
|
+
throw bad(`/lake/list row without a decimal ${k}`, r);
|
|
48
|
+
return v;
|
|
49
|
+
}
|
|
50
|
+
function int(r, k) {
|
|
51
|
+
const v = r[k];
|
|
52
|
+
return typeof v === "number" && Number.isInteger(v) ? v : null;
|
|
53
|
+
}
|
|
54
|
+
function add(a, b) {
|
|
55
|
+
return subDecimal(a, `-${b}`.replace(/^--/, ""));
|
|
56
|
+
}
|
|
57
|
+
function parseConfig(x) {
|
|
58
|
+
const c = x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
|
|
59
|
+
return {
|
|
60
|
+
depositCap: dec(c, "deposit_cap"),
|
|
61
|
+
managerProfitShare: dec(c, "manager_profit_share"),
|
|
62
|
+
managerLossShare: dec(c, "manager_loss_share"),
|
|
63
|
+
depositMinDurationMs: int(c, "deposit_min_duration_ms"),
|
|
64
|
+
managerMinBalancePortion: dec(c, "manager_min_balance_portion"),
|
|
65
|
+
managerLiquidationBalancePortion: dec(c, "manager_liquidation_balance_portion"),
|
|
66
|
+
withdrawWindowS: int(c, "withdraw_window_s"),
|
|
67
|
+
withdrawDurationS: int(c, "withdraw_duration_s"),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function parseVault(x) {
|
|
71
|
+
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
72
|
+
throw bad("/lake/list row is not an object", x);
|
|
73
|
+
const r = x;
|
|
74
|
+
const address = str(r, "address");
|
|
75
|
+
if (address === null)
|
|
76
|
+
throw bad("/lake/list row without an address", r);
|
|
77
|
+
const lpShares = reqDec(r, "lp_shares");
|
|
78
|
+
const managerShares = reqDec(r, "manager_shares");
|
|
79
|
+
const lpBalance = reqDec(r, "lp_balance");
|
|
80
|
+
const managerBalance = reqDec(r, "manager_balance");
|
|
81
|
+
const created = r.created_at;
|
|
82
|
+
return {
|
|
83
|
+
schema: 1,
|
|
84
|
+
address,
|
|
85
|
+
nickname: str(r, "nickname"),
|
|
86
|
+
creator: str(r, "creator"),
|
|
87
|
+
manager: str(r, "manager"),
|
|
88
|
+
referrer: str(r, "referrer"),
|
|
89
|
+
createdAt: typeof created === "number" && created > 0 ? created : null,
|
|
90
|
+
lpShares, managerShares, lpBalance, managerBalance,
|
|
91
|
+
tvl: add(lpBalance, managerBalance),
|
|
92
|
+
equitySnapshot: dec(r, "last_checked_equity"),
|
|
93
|
+
highWatermark: dec(r, "high_watermark"),
|
|
94
|
+
navPerLpShare: divDecimal(lpBalance, lpShares, 12),
|
|
95
|
+
navPerManagerShare: divDecimal(managerBalance, managerShares, 12),
|
|
96
|
+
apy: null,
|
|
97
|
+
config: parseConfig(r.config),
|
|
98
|
+
userShares: dec(r, "user_share"),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** GET /lake/list `data` (`{lakes: [...]}`). */
|
|
102
|
+
export function parseVaultList(data) {
|
|
103
|
+
const rows = data !== null && typeof data === "object" && !Array.isArray(data) ? data.lakes : undefined;
|
|
104
|
+
if (!Array.isArray(rows))
|
|
105
|
+
throw bad("/lake/list without a lakes list", data);
|
|
106
|
+
return rows.map(parseVault);
|
|
107
|
+
}
|
|
108
|
+
export async function fetchVaults(client, account) {
|
|
109
|
+
const env = await client.getEnvelope("/lake/list", account !== null ? { account } : {});
|
|
110
|
+
return parseVaultList(envelopeData(env, "/lake/list"));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Vault by address, or by a nickname (case-insensitive) that matches EXACTLY ONE vault. Nicknames
|
|
114
|
+
* are not unique in practice (live 2026-10-19: "Pacifica Vault" / "Pacifica vault", "Shark vault" /
|
|
115
|
+
* "shark vault"), so two or more matches are INVALID_PARAMS listing every candidate — never the
|
|
116
|
+
* first / newest one (a copycat vault must not receive a deposit).
|
|
117
|
+
*/
|
|
118
|
+
export function findVault(vaults, ref) {
|
|
119
|
+
const r = ref.trim();
|
|
120
|
+
const byAddress = vaults.find((x) => x.address === r);
|
|
121
|
+
if (byAddress !== undefined)
|
|
122
|
+
return byAddress;
|
|
123
|
+
const matches = vaults.filter((x) => x.nickname !== null && x.nickname.toLowerCase() === r.toLowerCase());
|
|
124
|
+
if (matches.length === 1)
|
|
125
|
+
return matches[0];
|
|
126
|
+
if (matches.length === 0) {
|
|
127
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `No Pacifica vault "${ref}" (address or nickname)`, {
|
|
128
|
+
exchange: "pacifica", remediation: "List them: perp -e pacifica vault list",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const candidates = matches.map((x) => ({ address: x.address, nickname: x.nickname, manager: x.manager, tvl: x.tvl }));
|
|
132
|
+
throw new PerpError("INVALID_PARAMS", `Vault nickname "${ref}" is ambiguous — ${matches.length} vaults match: ${candidates.map((c) => `${c.address} (${c.nickname}, manager ${c.manager ?? "none"}, TVL ${c.tvl})`).join("; ")}`, {
|
|
133
|
+
exchange: "pacifica", candidates, remediation: "Pass the vault ADDRESS (check its manager first: perp -e pacifica vault info <address>)",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** UNOFFICIAL endpoint (not in the API docs; used by app.pacifica.fi). Advisory only — the venue's own refusal is final. */
|
|
137
|
+
export const DEPOSIT_WHITELIST_API_STATUS = "unofficial";
|
|
138
|
+
export async function fetchDepositWhitelist(client, vault) {
|
|
139
|
+
const data = envelopeData(await client.getEnvelope("/lake/get_deposit_whitelist", { lake: vault }), "/lake/get_deposit_whitelist");
|
|
140
|
+
const a = data !== null && typeof data === "object" ? data.addresses : undefined;
|
|
141
|
+
if (!Array.isArray(a) || !a.every((s) => typeof s === "string"))
|
|
142
|
+
throw bad("/lake/get_deposit_whitelist without an addresses list", data);
|
|
143
|
+
return a;
|
|
144
|
+
}
|
|
145
|
+
/** docs: open while current_unix_time_s mod withdraw_window_s < withdraw_duration_s (epoch-anchored). */
|
|
146
|
+
export function withdrawWindowAt(c, nowMs) {
|
|
147
|
+
if (c.withdrawWindowS === null || c.withdrawDurationS === null || c.withdrawWindowS <= 0)
|
|
148
|
+
return { restricted: false, open: true, nextChangeAt: null };
|
|
149
|
+
const now = Math.floor(nowMs / 1000);
|
|
150
|
+
const start = now - (now % c.withdrawWindowS);
|
|
151
|
+
const open = now % c.withdrawWindowS < c.withdrawDurationS;
|
|
152
|
+
return { restricted: true, open, nextChangeAt: (open ? start + c.withdrawDurationS : start + c.withdrawWindowS) * 1000 };
|
|
153
|
+
}
|
|
154
|
+
/** Shares this account holds in the vault: manager shares for the manager, else LP `user_share`. */
|
|
155
|
+
export function heldShares(v, account) {
|
|
156
|
+
if (v.manager === account)
|
|
157
|
+
return { role: "manager", shares: v.managerShares, nav: v.navPerManagerShare };
|
|
158
|
+
return { role: "lp", shares: v.userShares, nav: v.navPerLpShare };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Validate a deposit (docs vaults/depositing: min 10 USDC, deposit cap on the LP balance,
|
|
162
|
+
* available_to_withdraw, deposit whitelist). Throws INVALID_PARAMS / INSUFFICIENT_BALANCE / POLICY_DENIED.
|
|
163
|
+
*/
|
|
164
|
+
export function planVaultDeposit(v, account, amountRaw, ctx) {
|
|
165
|
+
const amount = requireUsdcAmount(amountRaw, "vault deposit");
|
|
166
|
+
const where = `vault ${v.nickname ?? v.address}`;
|
|
167
|
+
if (cmpDecimal(amount, VAULT_MIN_DEPOSIT_USDC) < 0) {
|
|
168
|
+
throw new PerpError("INVALID_PARAMS", `${where}: deposit ${amount} USDC is below the ${VAULT_MIN_DEPOSIT_USDC} USDC minimum`, { exchange: "pacifica", remediation: `Deposit at least ${VAULT_MIN_DEPOSIT_USDC} USDC` });
|
|
169
|
+
}
|
|
170
|
+
const { role } = heldShares(v, account);
|
|
171
|
+
// NAV of the class receiving the deposit must be defined and positive (docs: shares = amount ×
|
|
172
|
+
// shares ÷ balance; 1:1 only when the class is EMPTY). Live: XAU_Stable_Growth had lp_shares 10,
|
|
173
|
+
// lp_balance 0 — a deposit there would be priced at NAV 0.
|
|
174
|
+
const [clsShares, clsBalance] = role === "lp" ? [v.lpShares, v.lpBalance] : [v.managerShares, v.managerBalance];
|
|
175
|
+
const sharesZero = cmpDecimal(clsShares, "0") === 0;
|
|
176
|
+
if (sharesZero ? cmpDecimal(clsBalance, "0") !== 0 : cmpDecimal(clsBalance, "0") <= 0) {
|
|
177
|
+
throw new PerpError("INVALID_PARAMS", `${where}: ${role} NAV is ${sharesZero ? "undefined" : "zero"} (${role} shares ${clsShares}, balance ${clsBalance}) — a deposit cannot be priced`, {
|
|
178
|
+
exchange: "pacifica", remediation: "Pick another vault (perp -e pacifica vault list)",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (role === "lp" && v.config.depositCap !== null && cmpDecimal(add(v.lpBalance, amount), v.config.depositCap) > 0) {
|
|
182
|
+
throw new PerpError("INVALID_PARAMS", `${where}: LP balance ${v.lpBalance} + ${amount} exceeds the deposit cap ${v.config.depositCap} USDC`, {
|
|
183
|
+
exchange: "pacifica", remediation: `Deposit at most ${subDecimal(v.config.depositCap, v.lpBalance)} USDC`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (cmpDecimal(ctx.availableToWithdraw, amount) < 0) {
|
|
187
|
+
throw new PerpError("INSUFFICIENT_BALANCE", `${where}: deposit ${amount} USDC exceeds available_to_withdraw ${ctx.availableToWithdraw}`, { exchange: "pacifica", remediation: "Deposit less, or free margin first (perp -e pacifica portfolio)" });
|
|
188
|
+
}
|
|
189
|
+
const warnings = [
|
|
190
|
+
"A vault is a third-party trading pool: you can lose up to the whole deposit (Pacifica does not vet or insure managers).",
|
|
191
|
+
`Manager: ${v.manager ?? "none"} — verify this address is the manager you intend to fund.`,
|
|
192
|
+
];
|
|
193
|
+
// Unofficial whitelist endpoint: advisory only; the venue's refusal is final.
|
|
194
|
+
if (ctx.whitelist === null)
|
|
195
|
+
warnings.push("Deposit whitelist unknown (unofficial endpoint unreadable); the venue refuses if the vault restricts depositors.");
|
|
196
|
+
else if (ctx.whitelist.length > 0 && !ctx.whitelist.includes(account))
|
|
197
|
+
warnings.push(`The vault's deposit whitelist (unofficial endpoint) lists ${ctx.whitelist.length} address(es), not ${account}: the venue will likely refuse.`);
|
|
198
|
+
const lock = v.config.depositMinDurationMs;
|
|
199
|
+
if (lock !== null && lock > 0)
|
|
200
|
+
warnings.push(`Lock-up: no withdrawal for ${(lock / 3_600_000).toFixed(2)} h after this deposit — and the timer restarts for your WHOLE balance in this vault.`);
|
|
201
|
+
if (v.config.withdrawWindowS !== null && v.config.withdrawDurationS !== null) {
|
|
202
|
+
warnings.push(`Withdrawals open only ${v.config.withdrawDurationS} s out of every ${v.config.withdrawWindowS} s (epoch-anchored window).`);
|
|
203
|
+
}
|
|
204
|
+
if (v.manager === null)
|
|
205
|
+
warnings.push("The vault has no manager yet (fresh vault): nobody trades it until a manager claims the seat.");
|
|
206
|
+
if (v.config.managerProfitShare !== null)
|
|
207
|
+
warnings.push(`Manager performance fee: ${v.config.managerProfitShare} of profit above the high-water mark.`);
|
|
208
|
+
return {
|
|
209
|
+
schema: 1, vault: v.address, nickname: v.nickname, manager: v.manager, role, amount,
|
|
210
|
+
// docs: shares_minted = amount × shares ÷ balance of the class (1:1 when the class is empty)
|
|
211
|
+
estimatedShares: role === "lp"
|
|
212
|
+
? (cmpDecimal(v.lpShares, "0") === 0 ? amount : divDecimal(mulDecimal(amount, v.lpShares), v.lpBalance, 6))
|
|
213
|
+
: (cmpDecimal(v.managerShares, "0") === 0 ? amount : divDecimal(mulDecimal(amount, v.managerShares), v.managerBalance, 6)),
|
|
214
|
+
lockMs: lock, warnings,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Validate a withdrawal: shares held, withdrawal window. Throws POSITION_NOT_FOUND / INVALID_PARAMS / POLICY_DENIED. */
|
|
218
|
+
export function planVaultWithdraw(v, account, sharesRaw, ctx) {
|
|
219
|
+
const where = `vault ${v.nickname ?? v.address}`;
|
|
220
|
+
const held = heldShares(v, account);
|
|
221
|
+
if (held.shares === null || cmpDecimal(held.shares, "0") <= 0) {
|
|
222
|
+
throw new PerpError("POSITION_NOT_FOUND", `${account} holds no ${held.role} shares in ${where}`, { exchange: "pacifica", remediation: "See your vault positions: perp -e pacifica vault positions" });
|
|
223
|
+
}
|
|
224
|
+
let shares;
|
|
225
|
+
if (sharesRaw === "all")
|
|
226
|
+
shares = held.shares;
|
|
227
|
+
else {
|
|
228
|
+
const s = String(sharesRaw).trim();
|
|
229
|
+
if (!PLAIN_POSITIVE.test(s) || !(Number(s) > 0))
|
|
230
|
+
throw new PerpError("INVALID_PARAMS", `shares must be a positive decimal number without leading zeros (got "${sharesRaw}")`, { exchange: "pacifica", remediation: "Pass the shares to redeem, or --all" });
|
|
231
|
+
shares = trimDecimal(s);
|
|
232
|
+
}
|
|
233
|
+
if (cmpDecimal(shares, held.shares) > 0) {
|
|
234
|
+
throw new PerpError("SIZE_TOO_LARGE", `${where}: redeeming ${shares} shares exceeds the ${held.shares} held`, { exchange: "pacifica", remediation: `Redeem at most ${held.shares} (or --all)` });
|
|
235
|
+
}
|
|
236
|
+
const window = withdrawWindowAt(v.config, ctx.nowMs);
|
|
237
|
+
if (!window.open) {
|
|
238
|
+
throw new PerpError("POLICY_DENIED", `${where}: the withdrawal window is closed; it next opens at ${new Date(window.nextChangeAt).toISOString()}`, {
|
|
239
|
+
exchange: "pacifica", remediation: "Withdraw while the window is open (perp -e pacifica vault info <vault> shows it)",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const estimatedAmount = held.nav === null ? null : trimDecimal(mulDecimal(shares, held.nav));
|
|
243
|
+
const warnings = [];
|
|
244
|
+
const lock = v.config.depositMinDurationMs;
|
|
245
|
+
if (lock !== null && lock > 0)
|
|
246
|
+
warnings.push(`The venue refuses a withdrawal within ${(lock / 3_600_000).toFixed(2)} h of your most recent deposit into this vault (not checkable here).`);
|
|
247
|
+
if (estimatedAmount !== null && ctx.vaultAvailableToWithdraw !== null && cmpDecimal(estimatedAmount, ctx.vaultAvailableToWithdraw) > 0) {
|
|
248
|
+
warnings.push(`The vault's free balance ${ctx.vaultAvailableToWithdraw} USDC is below the ≈${estimatedAmount} USDC redeemed: the venue may pay only part.`);
|
|
249
|
+
}
|
|
250
|
+
return { schema: 1, vault: v.address, nickname: v.nickname, manager: v.manager, role: held.role, shares, heldShares: held.shares, estimatedAmount, destination: account, window, warnings };
|
|
251
|
+
}
|
|
252
|
+
// ── Write requests (pure) ───────────────────────────────────────────────
|
|
253
|
+
export function buildVaultDepositRequest(vault, amount, identity, opts) {
|
|
254
|
+
return buildPresigned({
|
|
255
|
+
type: "deposit_to_lake", path: "/lake/deposit", identity, timestamp: opts.timestamp, expiryWindow: opts.expiryWindow,
|
|
256
|
+
payload: { lake: vault, amount, idempotency_key: opts.idempotencyKey },
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
export function buildVaultWithdrawRequest(vault, shares, identity, opts) {
|
|
260
|
+
return buildPresigned({
|
|
261
|
+
type: "withdraw_from_lake", path: "/lake/withdraw", identity, timestamp: opts.timestamp, expiryWindow: opts.expiryWindow,
|
|
262
|
+
payload: { lake: vault, shares, idempotency_key: opts.idempotencyKey },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Keypair } from "@solana/web3.js";
|
|
2
2
|
import { PacificaClient, type Network } from "../pacifica/index.js";
|
|
3
|
-
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
|
|
3
|
+
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangePublicTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
|
|
4
4
|
import type { ResolvedSlippage } from "./slippage.js";
|
|
5
5
|
import type { SolanaSigner } from "../signer/index.js";
|
|
6
6
|
import type { AgentMeta } from "../settings.js";
|
|
@@ -8,7 +8,7 @@ import type { TimeInForce as PacificaTif } from "../pacifica/types/order.js";
|
|
|
8
8
|
import type { IocOutcome } from "./ioc-outcome.js";
|
|
9
9
|
import { type BuilderApproval, type BuilderDecision, type BuilderFeeGate, type BuilderTarget } from "./builder-fee.js";
|
|
10
10
|
/**
|
|
11
|
-
* CLI TIF → Pacifica TIF
|
|
11
|
+
* CLI TIF → Pacifica TIF. GTC / IOC / ALO / TOB are native; venue
|
|
12
12
|
* aliases GTT → GTC and GTX → ALO; no TIF → GTC (uniform default). FOK (never
|
|
13
13
|
* emulated) and unknown values → uniform CAPABILITY_UNSUPPORTED.
|
|
14
14
|
*/
|
|
@@ -114,6 +114,7 @@ export declare const PACIFICA_WITHDRAW_MIN_USDC = "1";
|
|
|
114
114
|
* else (other status, unparseable body, already a PerpError) → `e` unchanged (outcome unknown).
|
|
115
115
|
*/
|
|
116
116
|
export declare function pacificaWithdrawRejection(e: unknown, amount: string): unknown;
|
|
117
|
+
/** A POST the venue refused with HTTP 4xx — incl. RATE_LIMITED (429 / pre-send throttle, nothing sent). */
|
|
117
118
|
export declare function pacificaRefused(e: unknown): boolean;
|
|
118
119
|
/** A cancel the venue refused (HTTP 4xx), settled from the order's own history_by_id rows. */
|
|
119
120
|
export type PacificaCancelRefusal = {
|
|
@@ -156,6 +157,12 @@ export interface PacificaAmendResult {
|
|
|
156
157
|
}
|
|
157
158
|
/** Pacifica order_type of a stop / TP / SL order (stop_market, stop_limit, take_profit_*, stop_loss_*). */
|
|
158
159
|
export declare function isPacificaStopType(type: string): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Trade direction of a Pacifica fill `side` (position effect): open_long / close_short buy,
|
|
162
|
+
* open_short / close_long sell; bid / ask for rows that carry an order side. Anything else →
|
|
163
|
+
* EXCHANGE_ERROR (never a guessed direction). Used by the public tape and the account fills.
|
|
164
|
+
*/
|
|
165
|
+
export declare function pacificaFillSide(side: string, endpoint: string): "buy" | "sell";
|
|
159
166
|
export declare class PacificaAdapter implements ExchangeAdapter {
|
|
160
167
|
readonly name = "pacifica";
|
|
161
168
|
readonly chain = "solana";
|
|
@@ -213,6 +220,17 @@ export declare class PacificaAdapter implements ExchangeAdapter {
|
|
|
213
220
|
* mint) — never the agent, which cannot move funds.
|
|
214
221
|
*/
|
|
215
222
|
masterSolanaSigner(): SolanaSigner;
|
|
223
|
+
/**
|
|
224
|
+
* The active signer for an account request whose body the caller builds itself (Print / vault
|
|
225
|
+
* writes): `account` (master address) and `agentWallet` (agent pubkey, or null when the master
|
|
226
|
+
* signs). Resolving it signs nothing. NO_SIGNER_AVAILABLE / AGENT_EXPIRED as `_resolveSigner`.
|
|
227
|
+
*/
|
|
228
|
+
signingIdentity(): {
|
|
229
|
+
tier: "agent" | "master" | "pk";
|
|
230
|
+
account: string;
|
|
231
|
+
agentWallet: string | null;
|
|
232
|
+
signMessage: (msg: Uint8Array) => Promise<Uint8Array>;
|
|
233
|
+
};
|
|
216
234
|
/** Access the underlying Keypair (only available with LocalSolanaSigner). */
|
|
217
235
|
get keypair(): Keypair;
|
|
218
236
|
private _getPrices;
|
|
@@ -251,7 +269,7 @@ export declare class PacificaAdapter implements ExchangeAdapter {
|
|
|
251
269
|
clientOrderId?: string;
|
|
252
270
|
}): Promise<unknown>;
|
|
253
271
|
/**
|
|
254
|
-
* Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable
|
|
272
|
+
* Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable),
|
|
255
273
|
* parsed from the result's content (so copies of it work): its `fillReport` — the terminal
|
|
256
274
|
* history_by_id row (filled / cancelled / rejected) with a reported filled_amount — for the
|
|
257
275
|
* same order id as the create response. null otherwise — reduce-only orders (not polled),
|
|
@@ -321,7 +339,7 @@ export declare class PacificaAdapter implements ExchangeAdapter {
|
|
|
321
339
|
*/
|
|
322
340
|
editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
|
|
323
341
|
/**
|
|
324
|
-
* Amend that keeps the order's TIF
|
|
342
|
+
* Amend that keeps the order's TIF. `size` = new open size.
|
|
325
343
|
* TIF = `tif` when the caller gives one, else the TIF encoded in the order's generated
|
|
326
344
|
* client_order_id; unknown → uniform CAPABILITY_UNSUPPORTED (never guessed).
|
|
327
345
|
* - ALO: native edit (the venue re-creates the order as ALO, keeping its client_order_id).
|
|
@@ -368,7 +386,16 @@ export declare class PacificaAdapter implements ExchangeAdapter {
|
|
|
368
386
|
assetId?: number;
|
|
369
387
|
routeType?: number;
|
|
370
388
|
}): Promise<unknown>;
|
|
371
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Public tape (GET /trades). Every fill is listed TWICE — a `fulfill_taker` row and a
|
|
391
|
+
* `fulfill_maker` row, each with that party's position effect as `side` (live 2026-10-14:
|
|
392
|
+
* taker `open_short` paired with maker `close_short` at the same time / price / amount). The
|
|
393
|
+
* tape is the taker rows, with the taker's direction: open_long / close_short buy, open_short /
|
|
394
|
+
* close_long sell (the same mapping as the account fills, PACIFICA_FILL_*_SIDES). Was every row
|
|
395
|
+
* of both parties mapped through `side === "bid"` → all "sell". No de-duplication: two taker
|
|
396
|
+
* rows with equal time / price / amount are two fills (one order filling two makers).
|
|
397
|
+
*/
|
|
398
|
+
getRecentTrades(symbol: string, _limit?: number): Promise<ExchangePublicTrade[]>;
|
|
372
399
|
getFundingHistory(symbol: string, limit?: number): Promise<{
|
|
373
400
|
time: number;
|
|
374
401
|
rate: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { nonEmptyOrListed, symbolNotFound } from "./unknown-symbol.js";
|
|
2
|
+
import { withTapeSkipped } from "./interface.js";
|
|
1
3
|
import { PacificaClient } from "../pacifica/index.js";
|
|
4
|
+
import { isPacificaRateLimited } from "../pacifica/rate-limit.js";
|
|
2
5
|
import { LocalSolanaSigner } from "../signer/index.js";
|
|
3
6
|
import { isExpired } from "../agent-wallet/expiry.js";
|
|
7
|
+
import { isPacificaPerpetual } from "../ws/pacifica-instrument.js";
|
|
4
8
|
import { randomUUID } from "crypto";
|
|
5
9
|
import { PerpError, extractErrorMessage, isDefiniteOrderFailure, orderNotSent, orderOutcomeUnknown } from "../errors.js";
|
|
6
10
|
import { parseFiniteVenueNumber, venueRateField } from "../utils/numeric.js";
|
|
@@ -10,7 +14,7 @@ import { requireCapabilities } from "./venue.js";
|
|
|
10
14
|
import { PACIFICA_UUID_RE, pacificaVenue } from "./venues/pacifica.js";
|
|
11
15
|
import { approvalFromCeiling, builderGateOf, rateToBps, requireFeeBps, NO_BUILDER } from "./builder-fee.js";
|
|
12
16
|
/**
|
|
13
|
-
* CLI TIF → Pacifica TIF
|
|
17
|
+
* CLI TIF → Pacifica TIF. GTC / IOC / ALO / TOB are native; venue
|
|
14
18
|
* aliases GTT → GTC and GTX → ALO; no TIF → GTC (uniform default). FOK (never
|
|
15
19
|
* emulated) and unknown values → uniform CAPABILITY_UNSUPPORTED.
|
|
16
20
|
*/
|
|
@@ -20,7 +24,7 @@ export function pacificaTif(tif) {
|
|
|
20
24
|
return validateLegacyTif(pacificaVenue, requireCapabilities(pacificaVenue), tif);
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
|
-
* Generated client_order_id carrying the order's TIF (
|
|
27
|
+
* Generated client_order_id carrying the order's TIF (like Ondo's
|
|
24
28
|
* `pcli-<p|n>-…`): Pacifica takes a full UUID and never returns the TIF (GET /orders has no
|
|
25
29
|
* tif field), so amend recovers it from the id. Layout: a UUID v4 whose first 6 hex digits are
|
|
26
30
|
* `5ac1f` + TIF code (1 GTC, 2 IOC, 3 ALO, 4 TOB). The native edit keeps the client_order_id.
|
|
@@ -274,7 +278,10 @@ export function pacificaWithdrawRejection(e, amount) {
|
|
|
274
278
|
}
|
|
275
279
|
return new PerpError("ACTION_REJECTED", `Pacifica withdraw ${amount} USDC refused (nothing was withdrawn): ${body.error}`, details);
|
|
276
280
|
}
|
|
281
|
+
/** A POST the venue refused with HTTP 4xx — incl. RATE_LIMITED (429 / pre-send throttle, nothing sent). */
|
|
277
282
|
export function pacificaRefused(e) {
|
|
283
|
+
if (isPacificaRateLimited(e))
|
|
284
|
+
return true;
|
|
278
285
|
return !(e instanceof PerpError) && /^POST \S+ failed \(4\d\d\)/.test(extractErrorMessage(e));
|
|
279
286
|
}
|
|
280
287
|
/**
|
|
@@ -320,13 +327,13 @@ function requireInstrumentType(rows) {
|
|
|
320
327
|
}
|
|
321
328
|
return rows;
|
|
322
329
|
}
|
|
323
|
-
/** Perp markets of /info (spot rows such as SOL-USDC
|
|
330
|
+
/** Perp markets of /info: instrument_type "perpetual" only (spot rows such as SOL-USDC, or any other type, excluded). */
|
|
324
331
|
function perpInfoRows(rows) {
|
|
325
|
-
return requireInstrumentType(rows).filter(
|
|
332
|
+
return requireInstrumentType(rows).filter(isPacificaPerpetual);
|
|
326
333
|
}
|
|
327
|
-
/** Wire symbols of /info spot
|
|
328
|
-
function
|
|
329
|
-
return new Set(requireInstrumentType(rows).filter((m) => m
|
|
334
|
+
/** Wire symbols of /info markets that are not perps (spot, any other instrument_type). */
|
|
335
|
+
function nonPerpInfoSymbols(rows) {
|
|
336
|
+
return new Set(requireInstrumentType(rows).filter((m) => !isPacificaPerpetual(m)).map((m) => m.symbol));
|
|
330
337
|
}
|
|
331
338
|
/** Pacifica order_type of a stop / TP / SL order (stop_market, stop_limit, take_profit_*, stop_loss_*). */
|
|
332
339
|
export function isPacificaStopType(type) {
|
|
@@ -335,6 +342,18 @@ export function isPacificaStopType(type) {
|
|
|
335
342
|
/** Fill sides of GET /trades/history (position effect) plus the order sides bid / ask. */
|
|
336
343
|
const PACIFICA_FILL_BUY_SIDES = new Set(["open_long", "close_short", "bid"]);
|
|
337
344
|
const PACIFICA_FILL_SELL_SIDES = new Set(["open_short", "close_long", "ask"]);
|
|
345
|
+
/**
|
|
346
|
+
* Trade direction of a Pacifica fill `side` (position effect): open_long / close_short buy,
|
|
347
|
+
* open_short / close_long sell; bid / ask for rows that carry an order side. Anything else →
|
|
348
|
+
* EXCHANGE_ERROR (never a guessed direction). Used by the public tape and the account fills.
|
|
349
|
+
*/
|
|
350
|
+
export function pacificaFillSide(side, endpoint) {
|
|
351
|
+
if (PACIFICA_FILL_BUY_SIDES.has(side))
|
|
352
|
+
return "buy";
|
|
353
|
+
if (PACIFICA_FILL_SELL_SIDES.has(side))
|
|
354
|
+
return "sell";
|
|
355
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica ${endpoint} row with unknown side "${side}"`, { exchange: "pacifica" });
|
|
356
|
+
}
|
|
338
357
|
/** `created_at` (ms) of a Pacifica history row; non-numeric → EXCHANGE_ERROR (never NaN). */
|
|
339
358
|
function pacificaTime(row, endpoint) {
|
|
340
359
|
const t = Number(pacificaField(row, "created_at", endpoint));
|
|
@@ -491,6 +510,15 @@ export class PacificaAdapter {
|
|
|
491
510
|
remediation: "Select the master wallet (--ows <wallet> or perp wallet use <wallet>), or do it on the Pacifica website",
|
|
492
511
|
});
|
|
493
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* The active signer for an account request whose body the caller builds itself (Print / vault
|
|
515
|
+
* writes): `account` (master address) and `agentWallet` (agent pubkey, or null when the master
|
|
516
|
+
* signs). Resolving it signs nothing. NO_SIGNER_AVAILABLE / AGENT_EXPIRED as `_resolveSigner`.
|
|
517
|
+
*/
|
|
518
|
+
signingIdentity() {
|
|
519
|
+
const r = this._resolveSigner();
|
|
520
|
+
return { tier: r.tier, account: this.account, agentWallet: r.tier === "agent" ? r.address : null, signMessage: (msg) => r.signer.signMessage(msg) };
|
|
521
|
+
}
|
|
494
522
|
/** Access the underlying Keypair (only available with LocalSolanaSigner). */
|
|
495
523
|
get keypair() {
|
|
496
524
|
if (this._solanaSigner instanceof LocalSolanaSigner) {
|
|
@@ -552,7 +580,7 @@ export class PacificaAdapter {
|
|
|
552
580
|
this._getPrices(),
|
|
553
581
|
]);
|
|
554
582
|
const priceMap = new Map(prices.map((p) => [p.symbol, p]));
|
|
555
|
-
// /info also lists spot markets (e.g. SOL-USDC); those are not perps
|
|
583
|
+
// /info also lists spot markets (e.g. SOL-USDC); those are not perps.
|
|
556
584
|
const result = perpInfoRows(markets).map((m) => {
|
|
557
585
|
const p = priceMap.get(m.symbol);
|
|
558
586
|
// Derive sizeDecimals from lot_size (e.g. "0.001" → 3, "1" → 0)
|
|
@@ -579,7 +607,12 @@ export class PacificaAdapter {
|
|
|
579
607
|
return result;
|
|
580
608
|
}
|
|
581
609
|
async getOrderbook(symbol) {
|
|
582
|
-
const book = await this.client.getBook(symbol)
|
|
610
|
+
const book = await this.client.getBook(symbol).catch((e) => {
|
|
611
|
+
// GET /book for an unlisted symbol: HTTP 400 {"error":"Book not found: NOPE"} (live 2026-10-14).
|
|
612
|
+
if (e instanceof Error && /^GET \/book failed \(400\):.*Book not found/.test(e.message))
|
|
613
|
+
throw symbolNotFound("pacifica", symbol);
|
|
614
|
+
throw e;
|
|
615
|
+
});
|
|
583
616
|
return {
|
|
584
617
|
bids: book.l[0].map((e) => [e.p, e.a]),
|
|
585
618
|
asks: book.l[1].map((e) => [e.p, e.a]),
|
|
@@ -736,8 +769,8 @@ export class PacificaAdapter {
|
|
|
736
769
|
throw new Error("No private key configured — account data unavailable. Run: perp setup");
|
|
737
770
|
// GET /orders covers every market; spot orders (SOL-USDC) belong to the spot adapter.
|
|
738
771
|
const [orders, info] = await Promise.all([this.client.getOrders(this.account), this.client.getInfo()]);
|
|
739
|
-
const
|
|
740
|
-
return orders.filter((o) => !
|
|
772
|
+
const nonPerp = nonPerpInfoSymbols(info);
|
|
773
|
+
return orders.filter((o) => !nonPerp.has(String(o.symbol))).map((o) => {
|
|
741
774
|
const raw = o;
|
|
742
775
|
return {
|
|
743
776
|
orderId: String(raw.order_id),
|
|
@@ -786,7 +819,7 @@ export class PacificaAdapter {
|
|
|
786
819
|
const filled = parseFloat(String(latest.filled_amount ?? "0"));
|
|
787
820
|
if (status === "filled" || filled > 0) {
|
|
788
821
|
// Terminal row (filled / cancelled remainder) with a reported filled_amount = the
|
|
789
|
-
// order's final fill
|
|
822
|
+
// order's final fill. A non-terminal partial row is not final.
|
|
790
823
|
const reported = latest.filled_amount;
|
|
791
824
|
const terminal = status === "filled" || status === "cancelled" || status === "rejected";
|
|
792
825
|
if (terminal && reported !== undefined && reported !== null && String(reported).trim() !== ""
|
|
@@ -830,7 +863,7 @@ export class PacificaAdapter {
|
|
|
830
863
|
return result;
|
|
831
864
|
}
|
|
832
865
|
/**
|
|
833
|
-
* Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable
|
|
866
|
+
* Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable),
|
|
834
867
|
* parsed from the result's content (so copies of it work): its `fillReport` — the terminal
|
|
835
868
|
* history_by_id row (filled / cancelled / rejected) with a reported filled_amount — for the
|
|
836
869
|
* same order id as the create response. null otherwise — reduce-only orders (not polled),
|
|
@@ -891,7 +924,7 @@ export class PacificaAdapter {
|
|
|
891
924
|
*/
|
|
892
925
|
async limitOrder(symbol, side, price, size, opts) {
|
|
893
926
|
this.ensureSigner();
|
|
894
|
-
const tif = pacificaTif(opts?.tif); //
|
|
927
|
+
const tif = pacificaTif(opts?.tif); // common mapping; FOK → CAPABILITY_UNSUPPORTED
|
|
895
928
|
// The caller's client order id (validated UUID) is sent as given; without one a generated
|
|
896
929
|
// client_order_id records the TIF for a later TIF-preserving amend. Either way the id sent
|
|
897
930
|
// rides on the result (`clientOrderId`).
|
|
@@ -1034,7 +1067,7 @@ export class PacificaAdapter {
|
|
|
1034
1067
|
return this.client.editOrder({ symbol, order_id: Number(orderId), price, amount: size }, this.account, this.signMessage);
|
|
1035
1068
|
}
|
|
1036
1069
|
/**
|
|
1037
|
-
* Amend that keeps the order's TIF
|
|
1070
|
+
* Amend that keeps the order's TIF. `size` = new open size.
|
|
1038
1071
|
* TIF = `tif` when the caller gives one, else the TIF encoded in the order's generated
|
|
1039
1072
|
* client_order_id; unknown → uniform CAPABILITY_UNSUPPORTED (never guessed).
|
|
1040
1073
|
* - ALO: native edit (the venue re-creates the order as ALO, keeping its client_order_id).
|
|
@@ -1314,48 +1347,85 @@ export class PacificaAdapter {
|
|
|
1314
1347
|
return this.client.withdraw({ amount }, this.account, (msg) => master.signMessage(msg))
|
|
1315
1348
|
.catch((e) => { throw pacificaWithdrawRejection(e, amount); });
|
|
1316
1349
|
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Public tape (GET /trades). Every fill is listed TWICE — a `fulfill_taker` row and a
|
|
1352
|
+
* `fulfill_maker` row, each with that party's position effect as `side` (live 2026-10-14:
|
|
1353
|
+
* taker `open_short` paired with maker `close_short` at the same time / price / amount). The
|
|
1354
|
+
* tape is the taker rows, with the taker's direction: open_long / close_short buy, open_short /
|
|
1355
|
+
* close_long sell (the same mapping as the account fills, PACIFICA_FILL_*_SIDES). Was every row
|
|
1356
|
+
* of both parties mapped through `side === "bid"` → all "sell". No de-duplication: two taker
|
|
1357
|
+
* rows with equal time / price / amount are two fills (one order filling two makers).
|
|
1358
|
+
*/
|
|
1317
1359
|
async getRecentTrades(symbol, _limit = 20) {
|
|
1318
1360
|
const trades = await this.client.getTrades(symbol);
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1361
|
+
if (!Array.isArray(trades)) {
|
|
1362
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades returned no trade list (${JSON.stringify(trades).slice(0, 120)})`, { exchange: "pacifica" });
|
|
1363
|
+
}
|
|
1364
|
+
// The docs list event_type fulfill_taker / fulfill_maker and side open_long / open_short /
|
|
1365
|
+
// close_long / close_short without declaring the sets closed: a row with any other value is
|
|
1366
|
+
// skipped (stderr warning + meta.skippedRows), never given a guessed direction, and does not
|
|
1367
|
+
// fail the rest of the tape.
|
|
1368
|
+
const skippedWhy = [];
|
|
1369
|
+
const rows = [];
|
|
1370
|
+
for (const t of trades) {
|
|
1371
|
+
const ev = pacificaField(t, "event_type", "/trades");
|
|
1372
|
+
if (ev === "fulfill_maker")
|
|
1373
|
+
continue;
|
|
1374
|
+
if (ev !== "fulfill_taker") {
|
|
1375
|
+
skippedWhy.push(`undocumented event_type "${ev}"`);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
const side = pacificaField(t, "side", "/trades");
|
|
1379
|
+
if (!PACIFICA_FILL_BUY_SIDES.has(side) && !PACIFICA_FILL_SELL_SIDES.has(side)) {
|
|
1380
|
+
skippedWhy.push(`undocumented side "${side}"`);
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (rows.length >= _limit)
|
|
1384
|
+
continue;
|
|
1385
|
+
rows.push({
|
|
1386
|
+
time: pacificaTime(t, "/trades"),
|
|
1387
|
+
symbol,
|
|
1388
|
+
side: pacificaFillSide(side, "/trades"),
|
|
1389
|
+
price: pacificaField(t, "price", "/trades"),
|
|
1390
|
+
size: pacificaField(t, "amount", "/trades"),
|
|
1391
|
+
fee: null, // the public tape carries no fee
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
const skipped = skippedWhy.length === 0 ? null : { count: skippedWhy.length, reason: [...new Set(skippedWhy)].join(", ") };
|
|
1395
|
+
if (skipped !== null)
|
|
1396
|
+
process.stderr.write(`[pacifica] /trades: skipped ${skipped.count} row(s) — ${skipped.reason}\n`);
|
|
1397
|
+
return withTapeSkipped(await nonEmptyOrListed(this, symbol, rows), skipped);
|
|
1336
1398
|
}
|
|
1337
1399
|
async getFundingHistory(symbol, limit = 10) {
|
|
1338
|
-
|
|
1339
|
-
const data = (
|
|
1340
|
-
if (!Array.isArray(data))
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1400
|
+
// The client unwraps the {success, data} envelope: the answer is the row list itself.
|
|
1401
|
+
const data = await this.client.getFundingHistory(symbol, { limit });
|
|
1402
|
+
if (!Array.isArray(data)) {
|
|
1403
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica /funding_rate/history returned no row list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "pacifica" });
|
|
1404
|
+
}
|
|
1405
|
+
// `200 []` for an unlisted symbol (live): checked against the market directory.
|
|
1406
|
+
return nonEmptyOrListed(this, symbol, data.map((h) => {
|
|
1407
|
+
const oracle = h.oracle_price;
|
|
1408
|
+
return {
|
|
1409
|
+
time: pacificaTime(h, "/funding_rate/history"),
|
|
1410
|
+
rate: venueRateField(h.funding_rate, "funding history funding_rate", "pacifica"),
|
|
1411
|
+
// absent → null (unknown), never "0"
|
|
1412
|
+
price: (typeof oracle === "string" && oracle !== "") || (typeof oracle === "number" && Number.isFinite(oracle)) ? String(oracle) : null,
|
|
1413
|
+
};
|
|
1346
1414
|
}));
|
|
1347
1415
|
}
|
|
1348
1416
|
async getKlines(symbol, interval, startTime, endTime) {
|
|
1349
1417
|
const klines = await this.client.getKline(symbol, interval, startTime, endTime);
|
|
1350
|
-
|
|
1418
|
+
// `200 []` for an unlisted symbol (live): checked against the market directory.
|
|
1419
|
+
return nonEmptyOrListed(this, symbol, klines.map((k) => ({
|
|
1351
1420
|
time: k.t,
|
|
1352
1421
|
open: k.o,
|
|
1353
1422
|
high: k.h,
|
|
1354
1423
|
low: k.l,
|
|
1355
1424
|
close: k.c,
|
|
1356
1425
|
volume: k.v,
|
|
1426
|
+
quoteVolume: null, // /kline carries base volume only
|
|
1357
1427
|
trades: k.n,
|
|
1358
|
-
}));
|
|
1428
|
+
})));
|
|
1359
1429
|
}
|
|
1360
1430
|
/**
|
|
1361
1431
|
* Recent orders, newest first (GET /orders/history; PacificaClient.get already unwraps the
|
|
@@ -1402,15 +1472,11 @@ export class PacificaAdapter {
|
|
|
1402
1472
|
throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades/history returned no list (${JSON.stringify(rows).slice(0, 120)})`, { exchange: "pacifica" });
|
|
1403
1473
|
}
|
|
1404
1474
|
return rows.slice(0, limit).map((t) => {
|
|
1405
|
-
const side = pacificaField(t, "side", "/trades/history");
|
|
1406
|
-
const buy = PACIFICA_FILL_BUY_SIDES.has(side);
|
|
1407
|
-
if (!buy && !PACIFICA_FILL_SELL_SIDES.has(side)) {
|
|
1408
|
-
throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades/history row with unknown side "${side}"`, { exchange: "pacifica" });
|
|
1409
|
-
}
|
|
1475
|
+
const side = pacificaFillSide(pacificaField(t, "side", "/trades/history"), "/trades/history");
|
|
1410
1476
|
return {
|
|
1411
1477
|
time: pacificaTime(t, "/trades/history"),
|
|
1412
1478
|
symbol: pacificaField(t, "symbol", "/trades/history"),
|
|
1413
|
-
side
|
|
1479
|
+
side,
|
|
1414
1480
|
price: pacificaField(t, "price", "/trades/history"),
|
|
1415
1481
|
size: pacificaField(t, "amount", "/trades/history"),
|
|
1416
1482
|
fee: pacificaField(t, "fee", "/trades/history"),
|