pacifica-js-sdk 1.0.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 ADDED
@@ -0,0 +1,52 @@
1
+
2
+ # Pacifica JS SDK
3
+
4
+ Official JavaScript/TypeScript SDK for [Pacifica](https://pacifica.fi).
5
+ Converted from the official [Python SDK](https://github.com/pacifica-fi/python-sdk).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install pacifica-js-sdk
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Create a `.env` file with your private key (optional for public data):
16
+ ```
17
+ PRIVATE_KEY=your_base58_private_key
18
+ ```
19
+
20
+ Run the example:
21
+ ```bash
22
+ npx tsx examples/usage.ts
23
+ ```
24
+
25
+ ## Code Example
26
+
27
+ ```typescript
28
+ import { PacificaClient } from './src';
29
+
30
+ const client = new PacificaClient({
31
+ privateKey: "YOUR_BASE58_PRIVATE_KEY",
32
+ network: "testnet"
33
+ });
34
+
35
+ // REST API
36
+ const subaccounts = await client.listSubaccounts();
37
+
38
+ // WebSocket API
39
+ await client.connect();
40
+ await client.createMarketOrderWs({
41
+ symbol: "BTC",
42
+ side: "bid",
43
+ amount: "0.1"
44
+ });
45
+ ```
46
+
47
+ ## Features
48
+
49
+ - Full WebSocket trading support (Market, Limit, Cancel, Cancel All)
50
+ - REST API support
51
+ - Automatic request signing matching Python SDK logic
52
+ - TypeScript support
@@ -0,0 +1,117 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface PacificaClientConfig {
3
+ privateKey?: string;
4
+ network?: 'mainnet' | 'testnet';
5
+ }
6
+ export declare class PacificaClient extends EventEmitter {
7
+ private keypair?;
8
+ private restUrl;
9
+ private wsUrl;
10
+ private ws;
11
+ private wsConnected;
12
+ private pendingRequests;
13
+ private axiosInstance;
14
+ constructor(config: PacificaClientConfig);
15
+ connect(): Promise<void>;
16
+ close(): void;
17
+ private handleWsMessage;
18
+ private sendWsRequest;
19
+ private signAndPreparePayload;
20
+ /**
21
+ * Subscribe to a data source
22
+ */
23
+ subscribe(source: string, params?: any): Promise<void>;
24
+ /**
25
+ * Create a market order via WebSocket
26
+ */
27
+ createMarketOrderWs(params: {
28
+ symbol: string;
29
+ side: 'bid' | 'ask';
30
+ amount: string;
31
+ reduce_only?: boolean;
32
+ slippage_percent?: string;
33
+ client_order_id?: string;
34
+ }): Promise<any>;
35
+ /**
36
+ * Create a limit order via WebSocket
37
+ */
38
+ createLimitOrderWs(params: {
39
+ symbol: string;
40
+ side: 'bid' | 'ask';
41
+ amount: string;
42
+ price: string;
43
+ reduce_only?: boolean;
44
+ client_order_id?: string;
45
+ tif?: string;
46
+ }): Promise<any>;
47
+ /**
48
+ * Cancel an order via WebSocket
49
+ */
50
+ cancelOrderWs(params: {
51
+ symbol: string;
52
+ order_id?: string;
53
+ client_order_id?: string;
54
+ }): Promise<any>;
55
+ /**
56
+ * Cancel all orders via WebSocket
57
+ */
58
+ cancelAllOrdersWs(params: {
59
+ symbol?: string;
60
+ all_symbols?: boolean;
61
+ exclude_reduce_only?: boolean;
62
+ }): Promise<any>;
63
+ /**
64
+ * Helper to send signed REST requests
65
+ */
66
+ private sendRestRequest;
67
+ /**
68
+ * Create a market order via REST
69
+ */
70
+ createMarketOrderRest(params: {
71
+ symbol: string;
72
+ side: 'bid' | 'ask';
73
+ amount: string;
74
+ reduce_only?: boolean;
75
+ slippage_percent?: string;
76
+ client_order_id?: string;
77
+ }): Promise<any>;
78
+ /**
79
+ * Create a limit order via REST
80
+ */
81
+ createLimitOrderRest(params: {
82
+ symbol: string;
83
+ side: 'bid' | 'ask';
84
+ amount: string;
85
+ price: string;
86
+ reduce_only?: boolean;
87
+ client_order_id?: string;
88
+ tif?: string;
89
+ }): Promise<any>;
90
+ /**
91
+ * Cancel an order via REST
92
+ */
93
+ cancelOrderRest(params: {
94
+ symbol: string;
95
+ order_id?: string;
96
+ client_order_id?: string;
97
+ }): Promise<any>;
98
+ /**
99
+ * Cancel all orders via REST
100
+ */
101
+ cancelAllOrdersRest(params: {
102
+ all_symbols?: boolean;
103
+ exclude_reduce_only?: boolean;
104
+ }): Promise<any>;
105
+ /**
106
+ * List subaccounts via REST
107
+ */
108
+ listSubaccounts(): Promise<any>;
109
+ /**
110
+ * Get exchange information including all available markets
111
+ */
112
+ getMarkets(): Promise<any>;
113
+ /**
114
+ * Get current prices for all markets
115
+ */
116
+ getPrices(): Promise<any>;
117
+ }
package/dist/client.js ADDED
@@ -0,0 +1,284 @@
1
+ import { Keypair } from "@solana/web3.js";
2
+ import { WebSocket } from "ws";
3
+ import axios from "axios";
4
+ import bs58 from "bs58";
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ import { EventEmitter } from 'events';
7
+ import { MAINNET_REST_URL, MAINNET_WS_URL, TESTNET_REST_URL, TESTNET_WS_URL, DEFAULT_EXPIRY_WINDOW } from "./config.js";
8
+ import { signMessage } from "./utils.js";
9
+ export class PacificaClient extends EventEmitter {
10
+ constructor(config) {
11
+ super();
12
+ this.ws = null;
13
+ this.wsConnected = null;
14
+ this.pendingRequests = new Map();
15
+ if (config.privateKey) {
16
+ this.keypair = Keypair.fromSecretKey(bs58.decode(config.privateKey));
17
+ }
18
+ if (config.network === 'testnet') {
19
+ this.restUrl = TESTNET_REST_URL;
20
+ this.wsUrl = TESTNET_WS_URL;
21
+ }
22
+ else {
23
+ this.restUrl = MAINNET_REST_URL;
24
+ this.wsUrl = MAINNET_WS_URL;
25
+ }
26
+ this.axiosInstance = axios.create({
27
+ baseURL: this.restUrl,
28
+ headers: {
29
+ 'Content-Type': 'application/json'
30
+ }
31
+ });
32
+ }
33
+ // WebSocket Management
34
+ async connect() {
35
+ if (this.ws?.readyState === WebSocket.OPEN)
36
+ return;
37
+ this.ws = new WebSocket(this.wsUrl);
38
+ this.wsConnected = new Promise((resolve, reject) => {
39
+ const onOpen = () => {
40
+ this.ws?.removeListener('error', onError);
41
+ resolve();
42
+ this.emit('connected');
43
+ };
44
+ const onError = (err) => {
45
+ this.ws?.removeListener('open', onOpen);
46
+ reject(err);
47
+ };
48
+ this.ws?.once('open', onOpen);
49
+ this.ws?.once('error', onError);
50
+ });
51
+ this.ws.on('message', (data) => {
52
+ try {
53
+ const message = JSON.parse(data.toString());
54
+ this.handleWsMessage(message);
55
+ }
56
+ catch (err) {
57
+ console.error('Failed to parse WS message:', err);
58
+ }
59
+ });
60
+ this.ws.on('close', () => {
61
+ this.ws = null;
62
+ this.wsConnected = null;
63
+ this.emit('disconnected');
64
+ });
65
+ await this.wsConnected;
66
+ }
67
+ close() {
68
+ if (this.ws) {
69
+ this.ws.close();
70
+ }
71
+ }
72
+ handleWsMessage(message) {
73
+ if (message.id && this.pendingRequests.has(message.id)) {
74
+ const { resolve, reject } = this.pendingRequests.get(message.id);
75
+ if (message.error) {
76
+ reject(new Error(message.error.message || JSON.stringify(message.error)));
77
+ }
78
+ else {
79
+ resolve(message.result || message);
80
+ }
81
+ this.pendingRequests.delete(message.id);
82
+ }
83
+ else {
84
+ // Handle subscriptions or unsolicited messages
85
+ this.emit('message', message);
86
+ }
87
+ }
88
+ async sendWsRequest(method, payload) {
89
+ await this.connect();
90
+ const id = uuidv4();
91
+ const message = {
92
+ id,
93
+ params: {
94
+ [method]: payload
95
+ }
96
+ };
97
+ return new Promise((resolve, reject) => {
98
+ this.pendingRequests.set(id, { resolve, reject });
99
+ this.ws.send(JSON.stringify(message));
100
+ // Timeout after 30 seconds
101
+ setTimeout(() => {
102
+ if (this.pendingRequests.has(id)) {
103
+ this.pendingRequests.delete(id);
104
+ reject(new Error('Request timed out'));
105
+ }
106
+ }, 30000);
107
+ });
108
+ }
109
+ signAndPreparePayload(type, payload) {
110
+ if (!this.keypair) {
111
+ throw new Error("Private key is required for signing requests");
112
+ }
113
+ const timestamp = Date.now(); // Milliseconds
114
+ const expiry_window = DEFAULT_EXPIRY_WINDOW;
115
+ const header = {
116
+ timestamp,
117
+ expiry_window,
118
+ type
119
+ };
120
+ const { signature } = signMessage(header, payload, this.keypair);
121
+ return {
122
+ account: this.keypair.publicKey.toBase58(),
123
+ signature,
124
+ timestamp,
125
+ expiry_window,
126
+ ...payload
127
+ };
128
+ }
129
+ /**
130
+ * Subscribe to a data source
131
+ */
132
+ async subscribe(source, params = {}) {
133
+ await this.connect();
134
+ const message = {
135
+ method: "subscribe",
136
+ params: {
137
+ source,
138
+ ...params
139
+ }
140
+ };
141
+ this.ws.send(JSON.stringify(message));
142
+ }
143
+ // WebSocket Trading Operations
144
+ /**
145
+ * Create a market order via WebSocket
146
+ */
147
+ async createMarketOrderWs(params) {
148
+ const payload = {
149
+ symbol: params.symbol,
150
+ side: params.side,
151
+ amount: params.amount,
152
+ reduce_only: params.reduce_only ?? false,
153
+ slippage_percent: params.slippage_percent ?? "0.5",
154
+ client_order_id: params.client_order_id ?? uuidv4()
155
+ };
156
+ const signedPayload = this.signAndPreparePayload("create_market_order", payload);
157
+ return this.sendWsRequest("create_market_order", signedPayload);
158
+ }
159
+ /**
160
+ * Create a limit order via WebSocket
161
+ */
162
+ async createLimitOrderWs(params) {
163
+ const payload = {
164
+ symbol: params.symbol,
165
+ side: params.side,
166
+ amount: params.amount,
167
+ price: params.price,
168
+ reduce_only: params.reduce_only ?? false,
169
+ client_order_id: params.client_order_id ?? uuidv4(),
170
+ tif: params.tif || "GTC"
171
+ };
172
+ // Note: The method name in WS params is 'create_order', but type is 'create_order'
173
+ const signedPayload = this.signAndPreparePayload("create_order", payload);
174
+ return this.sendWsRequest("create_order", signedPayload);
175
+ }
176
+ /**
177
+ * Cancel an order via WebSocket
178
+ */
179
+ async cancelOrderWs(params) {
180
+ const payload = {
181
+ symbol: params.symbol
182
+ };
183
+ if (params.order_id)
184
+ payload.order_id = params.order_id;
185
+ if (params.client_order_id)
186
+ payload.client_order_id = params.client_order_id;
187
+ const signedPayload = this.signAndPreparePayload("cancel_order", payload);
188
+ return this.sendWsRequest("cancel_order", signedPayload);
189
+ }
190
+ /**
191
+ * Cancel all orders via WebSocket
192
+ */
193
+ async cancelAllOrdersWs(params) {
194
+ const payload = {
195
+ all_symbols: params.all_symbols ?? true,
196
+ exclude_reduce_only: params.exclude_reduce_only ?? false
197
+ };
198
+ // Note: symbol is not used if all_symbols is true? Python code sends just these two.
199
+ const signedPayload = this.signAndPreparePayload("cancel_all_orders", payload);
200
+ return this.sendWsRequest("cancel_all_orders", signedPayload);
201
+ }
202
+ // REST API Methods
203
+ /**
204
+ * Helper to send signed REST requests
205
+ */
206
+ async sendRestRequest(endpoint, type, payload) {
207
+ const signedPayload = this.signAndPreparePayload(type, payload);
208
+ const response = await this.axiosInstance.post(endpoint, signedPayload);
209
+ return response.data;
210
+ }
211
+ /**
212
+ * Create a market order via REST
213
+ */
214
+ async createMarketOrderRest(params) {
215
+ const payload = {
216
+ symbol: params.symbol,
217
+ side: params.side,
218
+ amount: params.amount,
219
+ reduce_only: params.reduce_only ?? false,
220
+ slippage_percent: params.slippage_percent ?? "0.5",
221
+ client_order_id: params.client_order_id ?? uuidv4()
222
+ };
223
+ return this.sendRestRequest('/orders/create_market', "create_market_order", payload);
224
+ }
225
+ /**
226
+ * Create a limit order via REST
227
+ */
228
+ async createLimitOrderRest(params) {
229
+ const payload = {
230
+ symbol: params.symbol,
231
+ side: params.side,
232
+ amount: params.amount,
233
+ price: params.price,
234
+ reduce_only: params.reduce_only ?? false,
235
+ client_order_id: params.client_order_id ?? uuidv4(),
236
+ tif: params.tif || "GTC"
237
+ };
238
+ return this.sendRestRequest('/orders/create', "create_order", payload);
239
+ }
240
+ /**
241
+ * Cancel an order via REST
242
+ */
243
+ async cancelOrderRest(params) {
244
+ const payload = {
245
+ symbol: params.symbol
246
+ };
247
+ if (params.order_id)
248
+ payload.order_id = params.order_id;
249
+ if (params.client_order_id)
250
+ payload.client_order_id = params.client_order_id;
251
+ return this.sendRestRequest('/orders/cancel', "cancel_order", payload);
252
+ }
253
+ /**
254
+ * Cancel all orders via REST
255
+ */
256
+ async cancelAllOrdersRest(params) {
257
+ const payload = {
258
+ all_symbols: params.all_symbols ?? true,
259
+ exclude_reduce_only: params.exclude_reduce_only ?? false
260
+ };
261
+ return this.sendRestRequest('/orders/cancel_all', "cancel_all_orders", payload);
262
+ }
263
+ /**
264
+ * List subaccounts via REST
265
+ */
266
+ async listSubaccounts() {
267
+ return this.sendRestRequest('/account/subaccount/list', "list_subaccounts", {});
268
+ }
269
+ // Public REST Methods
270
+ /**
271
+ * Get exchange information including all available markets
272
+ */
273
+ async getMarkets() {
274
+ const response = await this.axiosInstance.get('/info');
275
+ return response.data;
276
+ }
277
+ /**
278
+ * Get current prices for all markets
279
+ */
280
+ async getPrices() {
281
+ const response = await this.axiosInstance.get('/info/prices');
282
+ return response.data;
283
+ }
284
+ }
@@ -0,0 +1,5 @@
1
+ export declare const MAINNET_REST_URL = "https://api.pacifica.fi/api/v1";
2
+ export declare const MAINNET_WS_URL = "wss://ws.pacifica.fi/ws";
3
+ export declare const TESTNET_REST_URL = "https://test-api.pacifica.fi/api/v1";
4
+ export declare const TESTNET_WS_URL = "wss://test-ws.pacifica.fi/ws";
5
+ export declare const DEFAULT_EXPIRY_WINDOW = 5000;
package/dist/config.js ADDED
@@ -0,0 +1,5 @@
1
+ export const MAINNET_REST_URL = "https://api.pacifica.fi/api/v1";
2
+ export const MAINNET_WS_URL = "wss://ws.pacifica.fi/ws";
3
+ export const TESTNET_REST_URL = "https://test-api.pacifica.fi/api/v1";
4
+ export const TESTNET_WS_URL = "wss://test-ws.pacifica.fi/ws";
5
+ export const DEFAULT_EXPIRY_WINDOW = 5000;
@@ -0,0 +1,4 @@
1
+ export * from "./config.js";
2
+ export * from "./types.js";
3
+ export * from "./utils.js";
4
+ export * from "./client.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./config.js";
2
+ export * from "./types.js";
3
+ export * from "./utils.js";
4
+ export * from "./client.js";
@@ -0,0 +1,16 @@
1
+ export interface SignatureHeader {
2
+ timestamp: number;
3
+ expiry_window: number;
4
+ type: string;
5
+ }
6
+ export interface SignaturePayload {
7
+ [key: string]: any;
8
+ }
9
+ export interface RequestHeader {
10
+ account: string;
11
+ signature: string;
12
+ timestamp: number;
13
+ expiry_window: number;
14
+ }
15
+ export interface SignedRequest extends RequestHeader, SignaturePayload {
16
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Keypair } from "@solana/web3.js";
2
+ import { SignatureHeader, SignaturePayload } from "./types.js";
3
+ /**
4
+ * Sorts object keys recursively to match Python's behavior
5
+ */
6
+ export declare function sortJsonKeys(value: any): any;
7
+ /**
8
+ * Prepares the message string for signing
9
+ */
10
+ export declare function prepareMessage(header: SignatureHeader, payload: SignaturePayload): string;
11
+ /**
12
+ * Signs the message using the provided keypair
13
+ */
14
+ export declare function signMessage(header: SignatureHeader, payload: SignaturePayload, keypair: Keypair): {
15
+ message: string;
16
+ signature: string;
17
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,48 @@
1
+ import bs58 from "bs58";
2
+ import nacl from "tweetnacl";
3
+ /**
4
+ * Sorts object keys recursively to match Python's behavior
5
+ */
6
+ export function sortJsonKeys(value) {
7
+ if (value === null || value === undefined) {
8
+ return value;
9
+ }
10
+ if (Array.isArray(value)) {
11
+ return value.map(sortJsonKeys);
12
+ }
13
+ if (typeof value === 'object') {
14
+ const sorted = {};
15
+ Object.keys(value)
16
+ .sort()
17
+ .forEach(key => {
18
+ sorted[key] = sortJsonKeys(value[key]);
19
+ });
20
+ return sorted;
21
+ }
22
+ return value;
23
+ }
24
+ /**
25
+ * Prepares the message string for signing
26
+ */
27
+ export function prepareMessage(header, payload) {
28
+ const data = {
29
+ ...header,
30
+ data: payload
31
+ };
32
+ const sortedData = sortJsonKeys(data);
33
+ // JSON.stringify without arguments produces compact JSON (no spaces)
34
+ // which matches Python's separators=(",", ":")
35
+ return JSON.stringify(sortedData);
36
+ }
37
+ /**
38
+ * Signs the message using the provided keypair
39
+ */
40
+ export function signMessage(header, payload, keypair) {
41
+ const message = prepareMessage(header, payload);
42
+ const messageBytes = new TextEncoder().encode(message);
43
+ const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
44
+ return {
45
+ message,
46
+ signature: bs58.encode(signatureBytes)
47
+ };
48
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "pacifica-js-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official JavaScript/TypeScript SDK for Pacifica",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "pacifica",
18
+ "sdk",
19
+ "crypto",
20
+ "trading",
21
+ "solana",
22
+ "defi"
23
+ ],
24
+ "author": "Pacifica",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "@solana/web3.js": "^1.98.4",
28
+ "axios": "^1.13.6",
29
+ "bs58": "^6.0.0",
30
+ "tweetnacl": "^1.0.3",
31
+ "uuid": "^13.0.0",
32
+ "ws": "^8.19.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^25.3.2",
36
+ "@types/uuid": "^10.0.0",
37
+ "@types/ws": "^8.18.1",
38
+ "dotenv": "^17.3.1",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }