perp-cli 0.12.17 → 0.14.1
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 +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +303 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +248 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +925 -245
- package/dist/commands/history.js +102 -54
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +357 -459
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +240 -6
- package/dist/exchanges/aster.js +1139 -195
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +152 -0
- package/dist/exchanges/ondo/adapter.js +811 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +95 -0
- package/dist/exchanges/ondo/funds.js +198 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +293 -4
- package/dist/exchanges/pacifica.js +1044 -90
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1386 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +29 -0
- package/dist/exchanges/venues/ondo.js +136 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +39 -0
- package/dist/exchanges/venues/qfex.js +140 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +821 -403
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/onchain-balances.d.ts +40 -0
- package/dist/onchain-balances.js +173 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +159 -55
- package/skills/perp-cli/references/agent-operations.md +112 -58
- package/skills/perp-cli/references/commands.md +269 -73
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
package/dist/pacifica/client.js
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildSignedRequest } from "./signing.js";
|
|
1
|
+
import { getNetworkConfig } from "./constants.js";
|
|
2
|
+
import { buildSignedRequest, buildAgentSignedRequest } from "./signing.js";
|
|
3
|
+
import { PerpError, extractErrorMessage, orderNotSent, orderOutcomeUnknown, sendFailure } from "../errors.js";
|
|
4
|
+
import { invalidateCache } from "../cache.js";
|
|
5
|
+
/** Per-request timeout of a POST (fetch + body read). */
|
|
6
|
+
export const PACIFICA_POST_TIMEOUT_MS = 15_000;
|
|
7
|
+
/**
|
|
8
|
+
* Account-setting writes whose HTTP 4xx is the venue's explicit refusal (nothing changed).
|
|
9
|
+
* docs.pacifica.fi (api-documentation/api/rest-api):
|
|
10
|
+
* account/update-leverage: "Status 400: Invalid request parameters" — {"error": "Invalid leverage", "code": 400}
|
|
11
|
+
* orders/create-position-tp-sl: "Status 400: Bad request" — {"error": "Position not found", "code": 400}
|
|
12
|
+
* Both pages list only "Status 500: Internal server error" besides.
|
|
13
|
+
*/
|
|
14
|
+
const DEFINITE_4XX_PATHS = new Set(["/positions/tpsl", "/account/leverage"]);
|
|
15
|
+
/** POST endpoints that only read (signed listings): a failure there is an ordinary read error. */
|
|
16
|
+
const READ_POST_PATHS = new Set(["/account/subaccount/list", "/agent/list", "/account/api_keys"]);
|
|
3
17
|
export class PacificaClient {
|
|
4
18
|
baseUrl;
|
|
5
19
|
apiKey;
|
|
6
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Agent-wallet pubkey for the current request signer, set by the adapter when an
|
|
22
|
+
* agent wallet (not the master) is signing. When present, signed bodies carry the
|
|
23
|
+
* `agent_wallet` field (with account = master) so Pacifica verifies the signature
|
|
24
|
+
* against the agent key. Undefined for master/PK signers.
|
|
25
|
+
*/
|
|
26
|
+
requestAgentWallet;
|
|
27
|
+
/** Attach (or clear) the agent wallet used for subsequent signed requests. */
|
|
28
|
+
setRequestAgentWallet(agentWallet) {
|
|
29
|
+
this.requestAgentWallet = agentWallet;
|
|
30
|
+
}
|
|
31
|
+
/** Build a signed POST body, attaching agent_wallet when an agent is the active signer. */
|
|
32
|
+
async signedBody(operationType, payload, account, signMessage) {
|
|
33
|
+
try {
|
|
34
|
+
return await (this.requestAgentWallet
|
|
35
|
+
? buildAgentSignedRequest(operationType, payload, account, this.requestAgentWallet, signMessage)
|
|
36
|
+
: buildSignedRequest(operationType, payload, account, signMessage));
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Signing failed before any request was sent: definitely not placed (never ORDER_PENDING).
|
|
40
|
+
throw orderNotSent("pacifica", operationType, e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
7
43
|
constructor(config = {}) {
|
|
8
44
|
this.baseUrl =
|
|
9
45
|
config.baseUrl || getNetworkConfig(config.network).restUrl;
|
|
10
46
|
this.apiKey = config.apiKey;
|
|
11
|
-
this.builderCode = config.builderCode || BUILDER_CODE;
|
|
12
47
|
}
|
|
13
48
|
headers() {
|
|
14
49
|
const h = { "Content-Type": "application/json" };
|
|
@@ -36,23 +71,77 @@ export class PacificaClient {
|
|
|
36
71
|
}
|
|
37
72
|
return json;
|
|
38
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Signed POST. Every path except the listing reads (READ_POST_PATHS) is an account write: once
|
|
76
|
+
* the request is on the wire a connection reset / timeout / 5xx / unreadable body leaves the
|
|
77
|
+
* outcome unknown → ORDER_PENDING (sendFailure), never a retryable network error. No connection
|
|
78
|
+
* at all → not sent. A 4xx is the venue's refusal and keeps the plain `POST <path> failed
|
|
79
|
+
* (<status>): <body>` error the adapters map (422 business codes) — except on
|
|
80
|
+
* DEFINITE_4XX_PATHS, where it is a definite ACTION_REJECTED (429 stays the plain error →
|
|
81
|
+
* RATE_LIMITED). A fetch timeout (PACIFICA_POST_TIMEOUT_MS) on a write is ORDER_PENDING too;
|
|
82
|
+
* on a listing read a timeout is TIMEOUT and any other transport failure EXCHANGE_UNREACHABLE.
|
|
83
|
+
*/
|
|
39
84
|
async post(path, body) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
const write = !READ_POST_PATHS.has(path);
|
|
86
|
+
const what = `POST ${path}`;
|
|
87
|
+
const remediation = `Do NOT resubmit — the request may have been executed. Check open orders, positions and balances first: perp -e pacifica account orders / portfolio`;
|
|
88
|
+
try {
|
|
89
|
+
let res;
|
|
90
|
+
let text;
|
|
91
|
+
try {
|
|
92
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: this.headers(),
|
|
95
|
+
body: JSON.stringify(body),
|
|
96
|
+
signal: AbortSignal.timeout(PACIFICA_POST_TIMEOUT_MS),
|
|
97
|
+
});
|
|
98
|
+
text = await res.text();
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
if (write)
|
|
102
|
+
throw sendFailure("pacifica", what, e, remediation);
|
|
103
|
+
// Signed listing read: nothing can have been executed — an ordinary retryable read failure.
|
|
104
|
+
const timedOut = e instanceof Error && (e.name === "TimeoutError" || e.name === "AbortError");
|
|
105
|
+
throw new PerpError(timedOut ? "TIMEOUT" : "EXCHANGE_UNREACHABLE", `Pacifica ${what} ${timedOut ? `timed out after ${PACIFICA_POST_TIMEOUT_MS} ms` : `failed: ${extractErrorMessage(e)}`} (read-only listing; nothing was changed)`, {
|
|
106
|
+
exchange: "pacifica",
|
|
107
|
+
remediation: "Retry; if it keeps failing check the network and Pacifica's status",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (write && res.status >= 500) {
|
|
111
|
+
throw orderOutcomeUnknown("pacifica", what, new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`), remediation);
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
if (DEFINITE_4XX_PATHS.has(path) && res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
115
|
+
throw new PerpError("ACTION_REJECTED", `Pacifica ${what} refused (HTTP ${res.status}, nothing was changed): ${text.slice(0, 300)}`, {
|
|
116
|
+
exchange: "pacifica",
|
|
117
|
+
httpStatus: res.status,
|
|
118
|
+
remediation: "The venue rejected the request — fix the parameters named in the message and retry",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`POST ${path} failed (${res.status}): ${text}`);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
if (!write)
|
|
128
|
+
throw e;
|
|
129
|
+
throw orderOutcomeUnknown("pacifica", what, new Error(`unreadable response (HTTP ${res.status}): ${text.slice(0, 200)}`), remediation);
|
|
130
|
+
}
|
|
48
131
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { ...payload, builder_code: this.builderCode };
|
|
132
|
+
finally {
|
|
133
|
+
// Every POST is an account write (order / cancel / TP/SL / leverage / transfer): drop the
|
|
134
|
+
// shared acct: disk cache so no later read serves the pre-write positions / orders.
|
|
135
|
+
invalidateCache("acct");
|
|
54
136
|
}
|
|
55
|
-
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* `builder_code` is attached only when the caller passes one — the adapter decides
|
|
140
|
+
* (settings builder.pacifica + confirmed approval, src/exchanges/builder-fee.ts).
|
|
141
|
+
* null = no builder code (the default, and always for spot: err 108).
|
|
142
|
+
*/
|
|
143
|
+
withBuilderCode(payload, builderCode) {
|
|
144
|
+
return builderCode === null ? payload : { ...payload, builder_code: builderCode };
|
|
56
145
|
}
|
|
57
146
|
// ==================== PUBLIC ENDPOINTS ====================
|
|
58
147
|
async getInfo() {
|
|
@@ -158,135 +247,150 @@ export class PacificaClient {
|
|
|
158
247
|
return this.get("/orders/twap", { account });
|
|
159
248
|
}
|
|
160
249
|
// ==================== AUTHENTICATED POST ENDPOINTS ====================
|
|
161
|
-
async createMarketOrder(params, account, signMessage) {
|
|
162
|
-
const payload = this.
|
|
163
|
-
const body = await
|
|
250
|
+
async createMarketOrder(params, account, signMessage, builderCode) {
|
|
251
|
+
const payload = this.withBuilderCode({ ...params }, builderCode);
|
|
252
|
+
const body = await this.signedBody("create_market_order", payload, account, signMessage);
|
|
164
253
|
return this.post("/orders/create_market", body);
|
|
165
254
|
}
|
|
166
|
-
async createLimitOrder(params, account, signMessage) {
|
|
167
|
-
const payload = this.
|
|
168
|
-
const body = await
|
|
255
|
+
async createLimitOrder(params, account, signMessage, builderCode) {
|
|
256
|
+
const payload = this.withBuilderCode({ ...params }, builderCode);
|
|
257
|
+
const body = await this.signedBody("create_order", payload, account, signMessage);
|
|
169
258
|
return this.post("/orders/create", body);
|
|
170
259
|
}
|
|
171
|
-
async createStopOrder(params, account, signMessage) {
|
|
172
|
-
|
|
260
|
+
async createStopOrder(params, account, signMessage, builderCode) {
|
|
261
|
+
// builder_code applies to every order-creation endpoint, stop orders included (builder-program docs).
|
|
262
|
+
const payload = this.withBuilderCode({ ...params }, builderCode);
|
|
263
|
+
const body = await this.signedBody("create_stop_order", payload, account, signMessage);
|
|
173
264
|
return this.post("/orders/stop/create", body);
|
|
174
265
|
}
|
|
175
266
|
async editOrder(params, account, signMessage) {
|
|
176
|
-
const body = await
|
|
267
|
+
const body = await this.signedBody("edit_order", params, account, signMessage);
|
|
177
268
|
return this.post("/orders/edit", body);
|
|
178
269
|
}
|
|
179
270
|
async cancelOrder(params, account, signMessage) {
|
|
180
|
-
const body = await
|
|
271
|
+
const body = await this.signedBody("cancel_order", params, account, signMessage);
|
|
181
272
|
return this.post("/orders/cancel", body);
|
|
182
273
|
}
|
|
183
274
|
async cancelAllOrders(params, account, signMessage) {
|
|
184
|
-
const body = await
|
|
275
|
+
const body = await this.signedBody("cancel_all_orders", params, account, signMessage);
|
|
185
276
|
return this.post("/orders/cancel_all", body);
|
|
186
277
|
}
|
|
187
278
|
async cancelStopOrder(params, account, signMessage) {
|
|
188
|
-
const body = await
|
|
279
|
+
const body = await this.signedBody("cancel_stop_order", params, account, signMessage);
|
|
189
280
|
return this.post("/orders/stop/cancel", body);
|
|
190
281
|
}
|
|
282
|
+
/** create_twap_order documents no builder_code, so a TWAP never carries one (builder-fee.ts). */
|
|
191
283
|
async createTWAP(params, account, signMessage) {
|
|
192
|
-
const
|
|
193
|
-
const body = await buildSignedRequest("create_twap_order", payload, account, signMessage);
|
|
284
|
+
const body = await this.signedBody("create_twap_order", { ...params }, account, signMessage);
|
|
194
285
|
return this.post("/orders/twap/create", body);
|
|
195
286
|
}
|
|
196
287
|
async cancelTWAP(params, account, signMessage) {
|
|
197
|
-
const body = await
|
|
288
|
+
const body = await this.signedBody("cancel_twap_order", params, account, signMessage);
|
|
198
289
|
return this.post("/orders/twap/cancel", body);
|
|
199
290
|
}
|
|
200
|
-
async setTPSL(params, account, signMessage) {
|
|
201
|
-
|
|
291
|
+
async setTPSL(params, account, signMessage, builderCode) {
|
|
292
|
+
// Position TP/SL creates orders and therefore takes builder_code too (POST /positions/tpsl).
|
|
293
|
+
const payload = this.withBuilderCode({ ...params }, builderCode);
|
|
294
|
+
const body = await this.signedBody("set_position_tpsl", payload, account, signMessage);
|
|
202
295
|
return this.post("/positions/tpsl", body);
|
|
203
296
|
}
|
|
204
297
|
async updateLeverage(params, account, signMessage) {
|
|
205
|
-
const body = await
|
|
298
|
+
const body = await this.signedBody("update_leverage", params, account, signMessage);
|
|
206
299
|
return this.post("/account/leverage", body);
|
|
207
300
|
}
|
|
208
301
|
async updateMarginMode(params, account, signMessage) {
|
|
209
|
-
const body = await
|
|
302
|
+
const body = await this.signedBody("update_margin_mode", params, account, signMessage);
|
|
210
303
|
return this.post("/account/margin", body);
|
|
211
304
|
}
|
|
212
305
|
async withdraw(params, account, signMessage) {
|
|
213
|
-
const body = await
|
|
306
|
+
const body = await this.signedBody("withdraw", params, account, signMessage);
|
|
214
307
|
return this.post("/account/withdraw", body);
|
|
215
308
|
}
|
|
216
309
|
// ==================== SUBACCOUNT ====================
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return this.post("/account/subaccount/create", confirmBody);
|
|
224
|
-
}
|
|
310
|
+
// POST /account/subaccount/create is not implemented. Docs (rest-api/subaccounts/create-subaccount):
|
|
311
|
+
// "Both the main account and subaccount must sign to establish the relationship." — the body is
|
|
312
|
+
// {main_account, subaccount, main_signature, sub_signature, timestamp, expiry_window}; the
|
|
313
|
+
// subaccount keypair signs {account: <main>} ("subaccount_initiate") and the main account signs
|
|
314
|
+
// {signature: <sub_signature>} ("subaccount_confirm") (python-sdk rest/create_subaccount.py).
|
|
315
|
+
// perp-cli has no second (subaccount) keypair source, so `manage sub create` is gated.
|
|
225
316
|
async listSubaccounts(account, signMessage) {
|
|
226
|
-
const body = await
|
|
317
|
+
const body = await this.signedBody("list_subaccounts", {}, account, signMessage);
|
|
227
318
|
return this.post("/account/subaccount/list", body);
|
|
228
319
|
}
|
|
229
320
|
async transferFunds(params, account, signMessage) {
|
|
230
|
-
const body = await
|
|
321
|
+
const body = await this.signedBody("transfer_funds", params, account, signMessage);
|
|
231
322
|
return this.post("/account/subaccount/transfer", body);
|
|
232
323
|
}
|
|
233
324
|
// ==================== AGENT WALLET ====================
|
|
234
325
|
async bindAgentWallet(agentWallet, account, signMessage) {
|
|
235
|
-
const body = await
|
|
326
|
+
const body = await this.signedBody("bind_agent_wallet", { agent_wallet: agentWallet }, account, signMessage);
|
|
236
327
|
return this.post("/agent/bind", body);
|
|
237
328
|
}
|
|
238
329
|
async listAgentWallets(account, signMessage) {
|
|
239
|
-
const body = await
|
|
330
|
+
const body = await this.signedBody("list_agent_wallets", {}, account, signMessage);
|
|
240
331
|
return this.post("/agent/list", body);
|
|
241
332
|
}
|
|
242
333
|
async revokeAgentWallet(agentWallet, account, signMessage) {
|
|
243
|
-
const body = await
|
|
334
|
+
const body = await this.signedBody("revoke_agent_wallet", { agent_wallet: agentWallet }, account, signMessage);
|
|
244
335
|
return this.post("/agent/revoke", body);
|
|
245
336
|
}
|
|
246
337
|
async revokeAllAgentWallets(account, signMessage) {
|
|
247
|
-
const body = await
|
|
338
|
+
const body = await this.signedBody("revoke_all_agent_wallets", {}, account, signMessage);
|
|
248
339
|
return this.post("/agent/revoke_all", body);
|
|
249
340
|
}
|
|
250
341
|
// ==================== API KEYS ====================
|
|
251
|
-
|
|
252
|
-
|
|
342
|
+
/**
|
|
343
|
+
* API Config Key (rate-limit key). Docs (api/rate-limits/api-config-keys): the request body is
|
|
344
|
+
* account / signature / timestamp / expiry_window only; python-sdk rest/api_config_keys.py signs
|
|
345
|
+
* type "create_api_key" with `signature_payload = {}`.
|
|
346
|
+
*/
|
|
347
|
+
async createApiKey(account, signMessage) {
|
|
348
|
+
const body = await this.signedBody("create_api_key", {}, account, signMessage);
|
|
253
349
|
return this.post("/account/api_keys/create", body);
|
|
254
350
|
}
|
|
255
351
|
async listApiKeys(account, signMessage) {
|
|
256
|
-
const body = await
|
|
352
|
+
const body = await this.signedBody("list_api_keys", {}, account, signMessage);
|
|
257
353
|
return this.post("/account/api_keys", body);
|
|
258
354
|
}
|
|
259
355
|
async revokeApiKey(apiKey, account, signMessage) {
|
|
260
|
-
const body = await
|
|
356
|
+
const body = await this.signedBody("revoke_api_key", { api_key: apiKey }, account, signMessage);
|
|
261
357
|
return this.post("/account/api_keys/revoke", body);
|
|
262
358
|
}
|
|
263
359
|
// ==================== LAKE ====================
|
|
264
360
|
async createLake(params, account, signMessage) {
|
|
265
|
-
|
|
361
|
+
// Optional fields are omitted, not sent as null (the signed payload must equal the body).
|
|
362
|
+
const payload = {};
|
|
363
|
+
if (params.manager !== null)
|
|
364
|
+
payload.manager = params.manager;
|
|
365
|
+
if (params.nickname !== null)
|
|
366
|
+
payload.nickname = params.nickname;
|
|
367
|
+
if (params.initial_deposit !== null)
|
|
368
|
+
payload.initial_deposit = params.initial_deposit;
|
|
369
|
+
const body = await this.signedBody("create_lake", payload, account, signMessage);
|
|
266
370
|
return this.post("/lake/create", body);
|
|
267
371
|
}
|
|
268
372
|
async depositToLake(params, account, signMessage) {
|
|
269
|
-
const body = await
|
|
373
|
+
const body = await this.signedBody("deposit_to_lake", params, account, signMessage);
|
|
270
374
|
return this.post("/lake/deposit", body);
|
|
271
375
|
}
|
|
272
376
|
async withdrawFromLake(params, account, signMessage) {
|
|
273
|
-
const body = await
|
|
377
|
+
const body = await this.signedBody("withdraw_from_lake", params, account, signMessage);
|
|
274
378
|
return this.post("/lake/withdraw", body);
|
|
275
379
|
}
|
|
276
380
|
// ==================== BUILDER CODE ====================
|
|
277
381
|
async approveBuilderCode(params, account, signMessage) {
|
|
278
|
-
const body = await
|
|
382
|
+
const body = await this.signedBody("approve_builder_code", params, account, signMessage);
|
|
279
383
|
return this.post("/account/builder_codes/approve", body);
|
|
280
384
|
}
|
|
281
385
|
async revokeBuilderCode(params, account, signMessage) {
|
|
282
|
-
const body = await
|
|
386
|
+
const body = await this.signedBody("revoke_builder_code", params, account, signMessage);
|
|
283
387
|
return this.post("/account/builder_codes/revoke", body);
|
|
284
388
|
}
|
|
285
389
|
async getBuilderApprovals(account) {
|
|
286
390
|
return this.get("/account/builder_codes/approvals", { account });
|
|
287
391
|
}
|
|
288
392
|
async updateBuilderFeeRate(params, account, signMessage) {
|
|
289
|
-
const body = await
|
|
393
|
+
const body = await this.signedBody("update_builder_code_fee_rate", params, account, signMessage);
|
|
290
394
|
return this.post("/builder/update_fee_rate", body);
|
|
291
395
|
}
|
|
292
396
|
async getBuilderOverview(account) {
|
|
@@ -300,7 +404,7 @@ export class PacificaClient {
|
|
|
300
404
|
}
|
|
301
405
|
// ==================== REFERRAL ====================
|
|
302
406
|
async claimReferralCode(params, account, signMessage) {
|
|
303
|
-
const body = await
|
|
407
|
+
const body = await this.signedBody("claim_referral_code", params, account, signMessage);
|
|
304
408
|
return this.post("/referral/user/code/claim", body);
|
|
305
409
|
}
|
|
306
410
|
// ==================== BATCH ====================
|
|
@@ -12,7 +12,6 @@ export declare const TESTNET_PACIFICA_VAULT = "5SDFdHZGTZbyRYu54CgmRkCGnPHC5pYaN
|
|
|
12
12
|
export declare const TESTNET_USDC_MINT = "USDPqRbLidFGufty2s3oizmDEKdqx7ePTqzDMbf5ZKM";
|
|
13
13
|
export declare const DEFAULT_EXPIRY_WINDOW = 5000;
|
|
14
14
|
export declare const USDC_DECIMALS = 6;
|
|
15
|
-
export declare const BUILDER_CODE = "PERPCLI";
|
|
16
15
|
export type Network = "mainnet" | "testnet";
|
|
17
16
|
export declare function getNetworkConfig(network?: Network): {
|
|
18
17
|
restUrl: string;
|
|
@@ -19,11 +19,6 @@ export const TESTNET_USDC_MINT = "USDPqRbLidFGufty2s3oizmDEKdqx7ePTqzDMbf5ZKM";
|
|
|
19
19
|
export const DEFAULT_EXPIRY_WINDOW = 5000; // 5 seconds
|
|
20
20
|
// USDC decimals
|
|
21
21
|
export const USDC_DECIMALS = 6;
|
|
22
|
-
// Builder code — 0% fee, used for Pacifica hackathon tracking
|
|
23
|
-
// The actual 3-way fallback (env → settings.referralCodes.pacifica → "PERPCLI") is
|
|
24
|
-
// resolved in index.ts before constructing PacificaAdapter. This constant is only
|
|
25
|
-
// a last-resort default for direct PacificaClient usage without a builderCode arg.
|
|
26
|
-
export const BUILDER_CODE = "PERPCLI";
|
|
27
22
|
export function getNetworkConfig(network = "mainnet") {
|
|
28
23
|
if (network === "testnet") {
|
|
29
24
|
return {
|
|
@@ -4,11 +4,38 @@ import { type Network } from "./constants.js";
|
|
|
4
4
|
* Build a Pacifica deposit instruction.
|
|
5
5
|
*
|
|
6
6
|
* @param userPubkey - User's wallet public key
|
|
7
|
-
* @param amount - Amount in USDC (e.g
|
|
7
|
+
* @param amount - Amount in USDC as a plain decimal string with at most 6 decimals (e.g. "100", "99.5");
|
|
8
|
+
* scaled exactly (no float math). Anything else → INVALID_PARAMS.
|
|
8
9
|
* @param network - mainnet or testnet
|
|
9
10
|
*/
|
|
10
|
-
export declare function buildDepositInstruction(userPubkey: PublicKey, amount:
|
|
11
|
+
export declare function buildDepositInstruction(userPubkey: PublicKey, amount: string, network?: Network): Promise<TransactionInstruction>;
|
|
11
12
|
/**
|
|
12
13
|
* Build and send a deposit transaction.
|
|
13
14
|
*/
|
|
14
|
-
export declare function deposit(connection: Connection, userPubkey: PublicKey, amount:
|
|
15
|
+
export declare function deposit(connection: Connection, userPubkey: PublicKey, amount: string, signTransaction: (tx: Transaction) => Promise<Transaction>, network?: Network): Promise<TransactionSignature>;
|
|
16
|
+
/** Highest relayer fee (USDC) the CLI signs for a relayed deposit. Above it the transaction is refused. */
|
|
17
|
+
export declare const PACIFICA_RELAY_FEE_CAP_USDC = 1;
|
|
18
|
+
/**
|
|
19
|
+
* Verify a RELAYER-BUILT transaction before the master Solana key signs it. The relayer is an
|
|
20
|
+
* untrusted HTTP service (PERP_RELAYER_URL): whatever it returns is signed only when it is
|
|
21
|
+
* exactly
|
|
22
|
+
* - ONE pinned Pacifica deposit instruction — the program id, every account (user, user USDC
|
|
23
|
+
* ATA, central state, vault, mint, …, in order, same signer / writable flags) and the data
|
|
24
|
+
* `buildDepositInstruction` would produce for `declared.netAmount`;
|
|
25
|
+
* - any number of ComputeBudget instructions (they move no funds);
|
|
26
|
+
* - at most ONE relayer fee transfer: an SPL Token Transfer / TransferChecked of exactly
|
|
27
|
+
* `declared.fee` USDC from the user's USDC account, authority = user, to the token account the
|
|
28
|
+
* build response declared (`feeRecipient`), with fee ≤ PACIFICA_RELAY_FEE_CAP_USDC;
|
|
29
|
+
* and fee + netAmount equals the amount the user asked to deposit, and the fee payer is not the
|
|
30
|
+
* user (the relayer sponsors gas). Anything else → POLICY_DENIED, nothing is signed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertRelayedDepositTransaction(tx: Transaction, ctx: {
|
|
33
|
+
user: PublicKey;
|
|
34
|
+
requestedAmount: string;
|
|
35
|
+
network: Network;
|
|
36
|
+
declared: {
|
|
37
|
+
fee: number;
|
|
38
|
+
netAmount: number;
|
|
39
|
+
feeRecipient: string | null;
|
|
40
|
+
};
|
|
41
|
+
}): Promise<void>;
|
package/dist/pacifica/deposit.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PublicKey, TransactionInstruction, Transaction, } from "@solana/web3.js";
|
|
2
2
|
import { getNetworkConfig, USDC_DECIMALS } from "./constants.js";
|
|
3
|
+
import { PerpError } from "../errors.js";
|
|
3
4
|
const TOKEN_PROGRAM = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
4
5
|
const ASSOCIATED_TOKEN_PROGRAM = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
5
6
|
const SYSTEM_PROGRAM = new PublicKey("11111111111111111111111111111111");
|
|
@@ -30,7 +31,8 @@ function getEventAuthority(programId) {
|
|
|
30
31
|
* Build a Pacifica deposit instruction.
|
|
31
32
|
*
|
|
32
33
|
* @param userPubkey - User's wallet public key
|
|
33
|
-
* @param amount - Amount in USDC (e.g
|
|
34
|
+
* @param amount - Amount in USDC as a plain decimal string with at most 6 decimals (e.g. "100", "99.5");
|
|
35
|
+
* scaled exactly (no float math). Anything else → INVALID_PARAMS.
|
|
34
36
|
* @param network - mainnet or testnet
|
|
35
37
|
*/
|
|
36
38
|
export async function buildDepositInstruction(userPubkey, amount, network = "mainnet") {
|
|
@@ -43,7 +45,12 @@ export async function buildDepositInstruction(userPubkey, amount, network = "mai
|
|
|
43
45
|
const eventAuthority = getEventAuthority(programId);
|
|
44
46
|
// Build instruction data: discriminator + amount (u64 LE)
|
|
45
47
|
const discriminator = await getDepositDiscriminator();
|
|
46
|
-
const amountLamports =
|
|
48
|
+
const amountLamports = usdcUnits(amount);
|
|
49
|
+
if (amountLamports === null || amountLamports <= 0n) {
|
|
50
|
+
throw new PerpError("INVALID_PARAMS", `Pacifica deposit amount "${amount}" is not a positive USDC amount with at most ${USDC_DECIMALS} decimals; nothing was built`, {
|
|
51
|
+
exchange: "pacifica", remediation: `Pass a plain decimal with at most ${USDC_DECIMALS} decimals, e.g. 100 or 99.5`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
47
54
|
const amountBytes = new Uint8Array(8);
|
|
48
55
|
const view = new DataView(amountBytes.buffer);
|
|
49
56
|
view.setBigUint64(0, amountLamports, true); // little-endian
|
|
@@ -76,3 +83,113 @@ export async function deposit(connection, userPubkey, amount, signTransaction, n
|
|
|
76
83
|
const signed = await signTransaction(tx);
|
|
77
84
|
return connection.sendRawTransaction(signed.serialize());
|
|
78
85
|
}
|
|
86
|
+
const COMPUTE_BUDGET_PROGRAM = new PublicKey("ComputeBudget111111111111111111111111111111");
|
|
87
|
+
/** Highest relayer fee (USDC) the CLI signs for a relayed deposit. Above it the transaction is refused. */
|
|
88
|
+
export const PACIFICA_RELAY_FEE_CAP_USDC = 1;
|
|
89
|
+
/** Decimal USDC string → integer base units (6 decimals), exact; null when not a plain decimal with ≤ 6 decimals. */
|
|
90
|
+
function usdcUnits(amount) {
|
|
91
|
+
const m = /^(\d+)(?:\.(\d{1,6}))?$/.exec(amount.trim());
|
|
92
|
+
if (m === null)
|
|
93
|
+
return null;
|
|
94
|
+
return BigInt(m[1]) * 10n ** BigInt(USDC_DECIMALS) + BigInt((m[2] ?? "").padEnd(USDC_DECIMALS, "0"));
|
|
95
|
+
}
|
|
96
|
+
function readU64LE(data, offset) {
|
|
97
|
+
if (data.length < offset + 8)
|
|
98
|
+
return null;
|
|
99
|
+
return new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Verify a RELAYER-BUILT transaction before the master Solana key signs it. The relayer is an
|
|
103
|
+
* untrusted HTTP service (PERP_RELAYER_URL): whatever it returns is signed only when it is
|
|
104
|
+
* exactly
|
|
105
|
+
* - ONE pinned Pacifica deposit instruction — the program id, every account (user, user USDC
|
|
106
|
+
* ATA, central state, vault, mint, …, in order, same signer / writable flags) and the data
|
|
107
|
+
* `buildDepositInstruction` would produce for `declared.netAmount`;
|
|
108
|
+
* - any number of ComputeBudget instructions (they move no funds);
|
|
109
|
+
* - at most ONE relayer fee transfer: an SPL Token Transfer / TransferChecked of exactly
|
|
110
|
+
* `declared.fee` USDC from the user's USDC account, authority = user, to the token account the
|
|
111
|
+
* build response declared (`feeRecipient`), with fee ≤ PACIFICA_RELAY_FEE_CAP_USDC;
|
|
112
|
+
* and fee + netAmount equals the amount the user asked to deposit, and the fee payer is not the
|
|
113
|
+
* user (the relayer sponsors gas). Anything else → POLICY_DENIED, nothing is signed.
|
|
114
|
+
*/
|
|
115
|
+
export async function assertRelayedDepositTransaction(tx, ctx) {
|
|
116
|
+
const refuse = (why) => new PerpError("POLICY_DENIED", `Refusing to sign the relayer-built Pacifica deposit: ${why}. Nothing was signed`, {
|
|
117
|
+
exchange: "pacifica", remediation: "Drop --relay for a direct deposit (you pay gas, ~0.005 SOL)",
|
|
118
|
+
});
|
|
119
|
+
const { user, declared } = ctx;
|
|
120
|
+
const requested = usdcUnits(ctx.requestedAmount);
|
|
121
|
+
if (requested === null || requested <= 0n)
|
|
122
|
+
throw refuse(`the requested amount "${ctx.requestedAmount}" is not a USDC amount with at most ${USDC_DECIMALS} decimals`);
|
|
123
|
+
if (!Number.isFinite(declared.fee) || !Number.isFinite(declared.netAmount) || declared.fee < 0 || declared.netAmount <= 0) {
|
|
124
|
+
throw refuse(`the build response declares fee ${declared.fee} / netAmount ${declared.netAmount}`);
|
|
125
|
+
}
|
|
126
|
+
const feeUnits = BigInt(Math.round(declared.fee * 10 ** USDC_DECIMALS));
|
|
127
|
+
const netUnits = BigInt(Math.round(declared.netAmount * 10 ** USDC_DECIMALS));
|
|
128
|
+
if (feeUnits + netUnits !== requested)
|
|
129
|
+
throw refuse(`declared fee ${declared.fee} + netAmount ${declared.netAmount} is not the requested ${ctx.requestedAmount} USDC`);
|
|
130
|
+
if (declared.fee > PACIFICA_RELAY_FEE_CAP_USDC)
|
|
131
|
+
throw refuse(`the relayer fee ${declared.fee} USDC is above the cap of ${PACIFICA_RELAY_FEE_CAP_USDC} USDC`);
|
|
132
|
+
if (tx.feePayer === undefined || tx.feePayer === null || tx.feePayer.equals(user))
|
|
133
|
+
throw refuse("the fee payer is not the relayer (a sponsored deposit never makes the user pay gas)");
|
|
134
|
+
const config = getNetworkConfig(ctx.network);
|
|
135
|
+
const usdcMint = new PublicKey(config.usdcMint);
|
|
136
|
+
const userAta = getATA(user, usdcMint);
|
|
137
|
+
const netDecimal = `${netUnits / 10n ** BigInt(USDC_DECIMALS)}.${(netUnits % 10n ** BigInt(USDC_DECIMALS)).toString().padStart(USDC_DECIMALS, "0")}`;
|
|
138
|
+
const expected = await buildDepositInstruction(user, netDecimal, ctx.network);
|
|
139
|
+
let deposits = 0;
|
|
140
|
+
let feeTransfers = 0;
|
|
141
|
+
for (const [i, ix] of tx.instructions.entries()) {
|
|
142
|
+
const at = `instruction ${i} (${ix.programId.toBase58()})`;
|
|
143
|
+
if (ix.programId.equals(COMPUTE_BUDGET_PROGRAM)) {
|
|
144
|
+
if (ix.keys.length !== 0)
|
|
145
|
+
throw refuse(`${at}: a compute-budget instruction with accounts`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ix.programId.equals(expected.programId)) {
|
|
149
|
+
const sameKeys = ix.keys.length === expected.keys.length && ix.keys.every((k, j) => k.pubkey.equals(expected.keys[j].pubkey) && k.isSigner === expected.keys[j].isSigner && k.isWritable === expected.keys[j].isWritable);
|
|
150
|
+
if (!sameKeys)
|
|
151
|
+
throw refuse(`${at}: the deposit accounts are not the pinned user / vault / mint accounts`);
|
|
152
|
+
if (!Buffer.from(ix.data).equals(Buffer.from(expected.data)))
|
|
153
|
+
throw refuse(`${at}: the deposit data is not "deposit ${declared.netAmount} USDC"`);
|
|
154
|
+
deposits++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (ix.programId.equals(TOKEN_PROGRAM)) {
|
|
158
|
+
// SPL Token Transfer (3): [source, destination, authority]; TransferChecked (12): [source, mint, destination, authority].
|
|
159
|
+
const tag = ix.data[0];
|
|
160
|
+
const checked = tag === 12;
|
|
161
|
+
if (tag !== 3 && !checked)
|
|
162
|
+
throw refuse(`${at}: an SPL token instruction that is not a transfer`);
|
|
163
|
+
if (ix.data.length !== (checked ? 10 : 9) || ix.keys.length !== (checked ? 4 : 3))
|
|
164
|
+
throw refuse(`${at}: a malformed token transfer`);
|
|
165
|
+
const [source, dest, authority] = checked ? [ix.keys[0], ix.keys[2], ix.keys[3]] : [ix.keys[0], ix.keys[1], ix.keys[2]];
|
|
166
|
+
if (checked && (!ix.keys[1].pubkey.equals(usdcMint) || ix.data[9] !== USDC_DECIMALS))
|
|
167
|
+
throw refuse(`${at}: the fee transfer is not in the deposit token`);
|
|
168
|
+
if (!source.pubkey.equals(userAta) || !authority.pubkey.equals(user))
|
|
169
|
+
throw refuse(`${at}: the token transfer is not from the user's own USDC account`);
|
|
170
|
+
if (declared.feeRecipient === null)
|
|
171
|
+
throw refuse(`${at}: a fee transfer the build response did not declare (no feeRecipient)`);
|
|
172
|
+
let recipient;
|
|
173
|
+
try {
|
|
174
|
+
recipient = new PublicKey(declared.feeRecipient);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
throw refuse(`the declared feeRecipient "${declared.feeRecipient}" is not a Solana address`);
|
|
178
|
+
}
|
|
179
|
+
if (!dest.pubkey.equals(recipient))
|
|
180
|
+
throw refuse(`${at}: the fee transfer goes to ${dest.pubkey.toBase58()}, not the declared ${recipient.toBase58()}`);
|
|
181
|
+
if (readU64LE(ix.data, 1) !== feeUnits)
|
|
182
|
+
throw refuse(`${at}: the fee transfer amount is not the declared ${declared.fee} USDC`);
|
|
183
|
+
if (++feeTransfers > 1)
|
|
184
|
+
throw refuse("more than one fee transfer");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
throw refuse(`${at} is not a Pacifica deposit, a compute-budget instruction or the declared fee transfer`);
|
|
188
|
+
}
|
|
189
|
+
if (deposits !== 1)
|
|
190
|
+
throw refuse(`it contains ${deposits} Pacifica deposit instructions (exactly one expected)`);
|
|
191
|
+
if (feeUnits > 0n && feeTransfers !== 1)
|
|
192
|
+
throw refuse(`the declared fee ${declared.fee} USDC is not transferred by the transaction`);
|
|
193
|
+
if (feeUnits === 0n && feeTransfers !== 0)
|
|
194
|
+
throw refuse("a fee transfer although the declared fee is 0");
|
|
195
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
import type { SolanaSigner } from "../signer/index.js";
|
|
3
|
+
export declare const PACIFICA_DEVNET_RPC = "https://api.devnet.solana.com";
|
|
4
|
+
export declare const MINT_TEST_USDC_COMPUTE_UNITS = 400000;
|
|
5
|
+
/** sha256("global:mint_test_usdc")[:8]. */
|
|
6
|
+
export declare function mintTestUsdcDiscriminator(): Buffer;
|
|
7
|
+
/** Plain positive decimal with ≤ 6 decimals → base units (u64). */
|
|
8
|
+
export declare function testUsdcBaseUnits(amount: string): bigint;
|
|
9
|
+
export interface MintTestUsdcPlan {
|
|
10
|
+
user: string;
|
|
11
|
+
userAccountPda: string;
|
|
12
|
+
userAta: string;
|
|
13
|
+
mint: string;
|
|
14
|
+
centralState: string;
|
|
15
|
+
programId: string;
|
|
16
|
+
amount: string;
|
|
17
|
+
baseUnits: string;
|
|
18
|
+
}
|
|
19
|
+
/** Accounts + amount the faucet instruction uses (pure; used for --dry-run). */
|
|
20
|
+
export declare function mintTestUsdcPlan(user: PublicKey, amount: string): MintTestUsdcPlan;
|
|
21
|
+
export declare function buildMintTestUsdcInstruction(plan: MintTestUsdcPlan): TransactionInstruction;
|
|
22
|
+
/**
|
|
23
|
+
* Build, sign, SIMULATE, then send + confirm the faucet transaction. A simulation
|
|
24
|
+
* error throws EXCHANGE_ERROR (with the program logs) and nothing is sent.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mintTestUsdc(connection: Connection, signer: SolanaSigner, amount: string): Promise<{
|
|
27
|
+
signature: string;
|
|
28
|
+
plan: MintTestUsdcPlan;
|
|
29
|
+
}>;
|