perps-sdk-ts 1.0.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/.claude/settings.local.json +11 -0
- package/CONTRACT_METHOD_FIXES.md +189 -0
- package/INTEGRATION_SUMMARY.md +219 -0
- package/OPTIMIZATION_GUIDE.md +238 -0
- package/README.md +384 -0
- package/SNAPSHOT_FIX_SUMMARY.md +161 -0
- package/SNAPSHOT_OPTIMIZATION_SUMMARY.md +199 -0
- package/dist/abis/Referral.d.ts +36 -0
- package/dist/abis/Referral.js +4 -0
- package/dist/abis/Trading.d.ts +57 -0
- package/dist/abis/Trading.js +742 -0
- package/dist/abis/erc20.d.ts +51 -0
- package/dist/abis/erc20.js +4 -0
- package/dist/abis/index.d.ts +8 -0
- package/dist/abis/index.js +24 -0
- package/dist/abis/multicall.d.ts +85 -0
- package/dist/abis/multicall.js +4 -0
- package/dist/abis/pairInfos.d.ts +77 -0
- package/dist/abis/pairInfos.js +4 -0
- package/dist/abis/pairStorage.d.ts +124 -0
- package/dist/abis/pairStorage.js +4 -0
- package/dist/abis/priceAggregator.d.ts +77 -0
- package/dist/abis/priceAggregator.js +4 -0
- package/dist/abis/tardingStorage.d.ts +97 -0
- package/dist/abis/tardingStorage.js +1295 -0
- package/dist/abis.d.ts +623 -0
- package/dist/abis.js +49 -0
- package/dist/client.d.ts +118 -0
- package/dist/client.js +224 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.js +42 -0
- package/dist/crypto/spki.d.ts +55 -0
- package/dist/crypto/spki.js +160 -0
- package/dist/feed/feed_client.d.ts +68 -0
- package/dist/feed/feed_client.js +239 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +87 -0
- package/dist/rpc/asset_parameters.d.ts +62 -0
- package/dist/rpc/asset_parameters.js +169 -0
- package/dist/rpc/blended.d.ts +23 -0
- package/dist/rpc/blended.js +55 -0
- package/dist/rpc/category_parameters.d.ts +34 -0
- package/dist/rpc/category_parameters.js +105 -0
- package/dist/rpc/delegation.d.ts +81 -0
- package/dist/rpc/delegation.js +180 -0
- package/dist/rpc/fee_parameters.d.ts +46 -0
- package/dist/rpc/fee_parameters.js +113 -0
- package/dist/rpc/multicall.d.ts +83 -0
- package/dist/rpc/multicall.js +117 -0
- package/dist/rpc/pair_info_queries.d.ts +101 -0
- package/dist/rpc/pair_info_queries.js +161 -0
- package/dist/rpc/pairs_cache.d.ts +62 -0
- package/dist/rpc/pairs_cache.js +240 -0
- package/dist/rpc/referral_operations.d.ts +67 -0
- package/dist/rpc/referral_operations.js +143 -0
- package/dist/rpc/snapshot.d.ts +49 -0
- package/dist/rpc/snapshot.js +162 -0
- package/dist/rpc/trade.d.ts +84 -0
- package/dist/rpc/trade.js +249 -0
- package/dist/rpc/trading_operations.d.ts +103 -0
- package/dist/rpc/trading_operations.js +295 -0
- package/dist/rpc/trading_parameters.d.ts +49 -0
- package/dist/rpc/trading_parameters.js +94 -0
- package/dist/signers/base.d.ts +24 -0
- package/dist/signers/base.js +10 -0
- package/dist/signers/kms.d.ts +47 -0
- package/dist/signers/kms.js +172 -0
- package/dist/signers/local.d.ts +43 -0
- package/dist/signers/local.js +64 -0
- package/dist/types.d.ts +1419 -0
- package/dist/types.js +245 -0
- package/dist/utils.d.ts +52 -0
- package/dist/utils.js +134 -0
- package/examples/advanced-queries.ts +181 -0
- package/examples/basic-usage.ts +78 -0
- package/examples/delegation-and-referrals.ts +130 -0
- package/examples/get-pyth-ids.ts +61 -0
- package/examples/kms-signer.ts +31 -0
- package/examples/optimized-snapshot.ts +153 -0
- package/examples/price-feed-with-sdk-ids.ts +97 -0
- package/examples/price-feed.ts +36 -0
- package/examples/trading-operations.ts +149 -0
- package/package.json +41 -0
- package/src/abis/Referral.ts +3 -0
- package/src/abis/Trading.ts +741 -0
- package/src/abis/erc20.ts +3 -0
- package/src/abis/index.ts +8 -0
- package/src/abis/multicall.ts +3 -0
- package/src/abis/pairInfos.ts +3 -0
- package/src/abis/pairStorage.ts +3 -0
- package/src/abis/priceAggregator.ts +3 -0
- package/src/abis/tardingStorage.ts +1294 -0
- package/src/abis.ts +56 -0
- package/src/client.ts +373 -0
- package/src/config.ts +62 -0
- package/src/crypto/spki.ts +197 -0
- package/src/feed/feed_client.ts +288 -0
- package/src/index.ts +114 -0
- package/src/rpc/asset_parameters.ts +217 -0
- package/src/rpc/blended.ts +77 -0
- package/src/rpc/category_parameters.ts +128 -0
- package/src/rpc/delegation.ts +225 -0
- package/src/rpc/fee_parameters.ts +150 -0
- package/src/rpc/multicall.ts +164 -0
- package/src/rpc/pair_info_queries.ts +208 -0
- package/src/rpc/pairs_cache.ts +268 -0
- package/src/rpc/referral_operations.ts +164 -0
- package/src/rpc/snapshot.ts +210 -0
- package/src/rpc/trade.ts +306 -0
- package/src/rpc/trading_operations.ts +378 -0
- package/src/rpc/trading_parameters.ts +127 -0
- package/src/signers/base.ts +27 -0
- package/src/signers/kms.ts +212 -0
- package/src/signers/local.ts +70 -0
- package/src/types.ts +410 -0
- package/src/utils.ts +155 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { PriceFeedResponse, PriceFeedResponseSchema } from "../types";
|
|
3
|
+
import { API_ENDPOINTS } from "../config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Callback function type for price updates
|
|
7
|
+
*/
|
|
8
|
+
export type PriceUpdateCallback = (priceData: PriceFeedResponse) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalize feed ID: remove 0x prefix and lowercase
|
|
12
|
+
* Pyth WebSocket sends IDs WITHOUT 0x prefix
|
|
13
|
+
*/
|
|
14
|
+
function normalizeFeedId(feedId: string): string {
|
|
15
|
+
let normalized = feedId.toLowerCase();
|
|
16
|
+
if (normalized.startsWith('0x')) {
|
|
17
|
+
normalized = normalized.slice(2);
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* WebSocket client for real-time price feeds from Pyth Network
|
|
24
|
+
*/
|
|
25
|
+
export class FeedClient {
|
|
26
|
+
private url: string;
|
|
27
|
+
private ws?: WebSocket;
|
|
28
|
+
private callbacks: Map<string, PriceUpdateCallback[]> = new Map();
|
|
29
|
+
private onError?: (error: Error) => void;
|
|
30
|
+
private onClose?: () => void;
|
|
31
|
+
private pairFeedMap: Map<string, string> = new Map();
|
|
32
|
+
private reconnectAttempts: number = 0;
|
|
33
|
+
private maxReconnectAttempts: number = 5;
|
|
34
|
+
private reconnectDelay: number = 1000;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
url: string = API_ENDPOINTS.PYTH_WS,
|
|
38
|
+
onError?: (error: Error) => void,
|
|
39
|
+
onClose?: () => void
|
|
40
|
+
) {
|
|
41
|
+
this.url = url;
|
|
42
|
+
this.onError = onError;
|
|
43
|
+
this.onClose = onClose;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register a callback for a specific price feed
|
|
48
|
+
*/
|
|
49
|
+
registerPriceFeedCallback(
|
|
50
|
+
feedId: string,
|
|
51
|
+
callback: PriceUpdateCallback
|
|
52
|
+
): void {
|
|
53
|
+
const id = normalizeFeedId(feedId);
|
|
54
|
+
if (!this.callbacks.has(id)) {
|
|
55
|
+
this.callbacks.set(id, []);
|
|
56
|
+
}
|
|
57
|
+
this.callbacks.get(id)!.push(callback);
|
|
58
|
+
|
|
59
|
+
if (this.callbacks.get(id)!.length === 1 && this.isConnected()) {
|
|
60
|
+
this.subscribeToPriceFeeds([id]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unregister a callback for a specific price feed
|
|
66
|
+
*/
|
|
67
|
+
unregisterPriceFeedCallback(
|
|
68
|
+
feedId: string,
|
|
69
|
+
callback: PriceUpdateCallback
|
|
70
|
+
): void {
|
|
71
|
+
const id = normalizeFeedId(feedId);
|
|
72
|
+
const cbs = this.callbacks.get(id);
|
|
73
|
+
if (!cbs) return;
|
|
74
|
+
|
|
75
|
+
const idx = cbs.indexOf(callback);
|
|
76
|
+
if (idx > -1) cbs.splice(idx, 1);
|
|
77
|
+
|
|
78
|
+
if (cbs.length === 0 && this.isConnected()) {
|
|
79
|
+
this.unsubscribeFromPriceFeeds([id]);
|
|
80
|
+
this.callbacks.delete(id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load pair to feed ID mappings
|
|
86
|
+
*/
|
|
87
|
+
loadPairFeeds(pairFeeds: Map<string, string>): void {
|
|
88
|
+
this.pairFeedMap = pairFeeds;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get feed ID for a trading pair
|
|
93
|
+
*/
|
|
94
|
+
getFeedIdForPair(pairName: string): string | undefined {
|
|
95
|
+
return this.pairFeedMap.get(pairName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Connect to WebSocket and listen for price updates
|
|
100
|
+
*/
|
|
101
|
+
async listenForPriceUpdates(): Promise<void> {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
try {
|
|
104
|
+
this.ws = new WebSocket(this.url);
|
|
105
|
+
|
|
106
|
+
this.ws.on("open", () => {
|
|
107
|
+
console.log("WebSocket connected to Pyth Network");
|
|
108
|
+
this.reconnectAttempts = 0;
|
|
109
|
+
|
|
110
|
+
const feedIds = Array.from(this.callbacks.keys());
|
|
111
|
+
if (feedIds.length > 0) {
|
|
112
|
+
this.subscribeToPriceFeeds(feedIds);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.ws.on("message", (data: WebSocket.Data) => {
|
|
119
|
+
try {
|
|
120
|
+
const message = JSON.parse(data.toString());
|
|
121
|
+
|
|
122
|
+
if (message.type !== "price_update") {
|
|
123
|
+
console.log("Received non-update message:", message);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (message.type === "price_update") {
|
|
127
|
+
this.handlePriceUpdate(message);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Error parsing WebSocket message:", error);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.ws.on("error", (error: Error) => {
|
|
135
|
+
console.error("WebSocket error:", error);
|
|
136
|
+
if (this.onError) {
|
|
137
|
+
this.onError(error);
|
|
138
|
+
}
|
|
139
|
+
reject(error);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.ws.on("close", () => {
|
|
143
|
+
console.log("WebSocket connection closed");
|
|
144
|
+
if (this.onClose) {
|
|
145
|
+
this.onClose();
|
|
146
|
+
}
|
|
147
|
+
this.attemptReconnect();
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
reject(error);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Subscribe to specific price feeds
|
|
157
|
+
*/
|
|
158
|
+
private subscribeToPriceFeeds(feedIds: string[]): void {
|
|
159
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
160
|
+
|
|
161
|
+
// Feed IDs should already be normalized, but ensure they are
|
|
162
|
+
const ids = feedIds.map(id => normalizeFeedId(id));
|
|
163
|
+
|
|
164
|
+
this.ws.send(JSON.stringify({ type: "subscribe", ids }));
|
|
165
|
+
console.log("Subscribed to Pyth feeds:", ids);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Unsubscribe from specific price feeds
|
|
170
|
+
*/
|
|
171
|
+
private unsubscribeFromPriceFeeds(feedIds: string[]): void {
|
|
172
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
173
|
+
|
|
174
|
+
const ids = feedIds.map(id => normalizeFeedId(id));
|
|
175
|
+
this.ws.send(JSON.stringify({ type: "unsubscribe", ids }));
|
|
176
|
+
console.log("Unsubscribed from Pyth feeds:", ids);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handle incoming price update
|
|
181
|
+
*/
|
|
182
|
+
private handlePriceUpdate(message: any): void {
|
|
183
|
+
try {
|
|
184
|
+
if (message.price_feed) {
|
|
185
|
+
const priceUpdate = message.price_feed;
|
|
186
|
+
|
|
187
|
+
// ⭐ KEY FIX: Normalize the incoming feed ID (Pyth sends without 0x)
|
|
188
|
+
const feedId = normalizeFeedId(priceUpdate.id);
|
|
189
|
+
|
|
190
|
+
const priceFeed = {
|
|
191
|
+
id: feedId,
|
|
192
|
+
price: {
|
|
193
|
+
price: priceUpdate.price.price,
|
|
194
|
+
conf: priceUpdate.price.conf,
|
|
195
|
+
expo: priceUpdate.price.expo,
|
|
196
|
+
publishTime: priceUpdate.price.publish_time,
|
|
197
|
+
},
|
|
198
|
+
emaPrice: {
|
|
199
|
+
price: priceUpdate.ema_price.price,
|
|
200
|
+
conf: priceUpdate.ema_price.conf,
|
|
201
|
+
expo: priceUpdate.ema_price.expo,
|
|
202
|
+
publishTime: priceUpdate.ema_price.publish_time,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const validatedPriceFeed = PriceFeedResponseSchema.parse(priceFeed);
|
|
207
|
+
|
|
208
|
+
// Trigger callbacks
|
|
209
|
+
const callbacks = this.callbacks.get(validatedPriceFeed.id);
|
|
210
|
+
if (callbacks && callbacks.length > 0) {
|
|
211
|
+
console.log(`✅ Triggering ${callbacks.length} callback(s) for ${validatedPriceFeed.id}`);
|
|
212
|
+
callbacks.forEach((callback) => {
|
|
213
|
+
try {
|
|
214
|
+
callback(validatedPriceFeed);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(
|
|
217
|
+
"Error in price update callback:",
|
|
218
|
+
error instanceof Error ? error.message : String(error)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
console.warn(`⚠️ No callbacks registered for feed: ${validatedPriceFeed.id}`);
|
|
224
|
+
console.log(`Registered feeds: [${Array.from(this.callbacks.keys()).join(", ")}]`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(
|
|
229
|
+
"Error processing price update:",
|
|
230
|
+
error instanceof Error ? error.message : String(error)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Attempt to reconnect to WebSocket
|
|
237
|
+
*/
|
|
238
|
+
private attemptReconnect(): void {
|
|
239
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
240
|
+
console.error("Max reconnection attempts reached");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.reconnectAttempts++;
|
|
245
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
246
|
+
|
|
247
|
+
console.log(
|
|
248
|
+
`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
this.listenForPriceUpdates().catch((error) => {
|
|
253
|
+
console.error("Reconnection failed:", error);
|
|
254
|
+
});
|
|
255
|
+
}, delay);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get latest prices via HTTP
|
|
260
|
+
*/
|
|
261
|
+
async getLatestPriceUpdates(feedIds: string[]): Promise<any> {
|
|
262
|
+
const url = `${API_ENDPOINTS.PYTH_HTTP}?ids[]=${feedIds.join("&ids[]=")}`;
|
|
263
|
+
|
|
264
|
+
const response = await fetch(url);
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
throw new Error(`Failed to fetch prices: ${response.statusText}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return await response.json();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Close the WebSocket connection
|
|
274
|
+
*/
|
|
275
|
+
close(): void {
|
|
276
|
+
if (this.ws) {
|
|
277
|
+
this.ws.close();
|
|
278
|
+
this.ws = undefined;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if WebSocket is connected
|
|
284
|
+
*/
|
|
285
|
+
isConnected(): boolean {
|
|
286
|
+
return this.ws !== undefined && this.ws.readyState === WebSocket.OPEN;
|
|
287
|
+
}
|
|
288
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avantis Trader SDK - TypeScript
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive SDK for interacting with the Avantis decentralized
|
|
5
|
+
* leveraged trading platform.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Main client
|
|
9
|
+
export { TraderClient } from './client';
|
|
10
|
+
|
|
11
|
+
// Signers
|
|
12
|
+
export { BaseSigner } from './signers/base';
|
|
13
|
+
export { LocalSigner } from './signers/local';
|
|
14
|
+
export { KMSSigner } from './signers/kms';
|
|
15
|
+
|
|
16
|
+
// Feed client
|
|
17
|
+
export { FeedClient, PriceUpdateCallback } from './feed/feed_client';
|
|
18
|
+
|
|
19
|
+
// RPC modules
|
|
20
|
+
export { PairsCache } from './rpc/pairs_cache';
|
|
21
|
+
export { AssetParametersRPC } from './rpc/asset_parameters';
|
|
22
|
+
export { CategoryParametersRPC } from './rpc/category_parameters';
|
|
23
|
+
export { FeeParametersRPC } from './rpc/fee_parameters';
|
|
24
|
+
export { TradingParametersRPC } from './rpc/trading_parameters';
|
|
25
|
+
export { BlendedRPC } from './rpc/blended';
|
|
26
|
+
export { TradeRPC } from './rpc/trade';
|
|
27
|
+
export { SnapshotRPC } from './rpc/snapshot';
|
|
28
|
+
export { TradingOperationsRPC } from './rpc/trading_operations';
|
|
29
|
+
export { DelegationRPC } from './rpc/delegation';
|
|
30
|
+
export { PairInfoQueriesRPC } from './rpc/pair_info_queries';
|
|
31
|
+
export { ReferralOperationsRPC } from './rpc/referral_operations';
|
|
32
|
+
export { MulticallRPC, type MulticallCall, type MulticallResult } from './rpc/multicall';
|
|
33
|
+
|
|
34
|
+
// Types and enums
|
|
35
|
+
export {
|
|
36
|
+
TradeInputOrderType,
|
|
37
|
+
MarginUpdateType,
|
|
38
|
+
type Spread,
|
|
39
|
+
type PairInfo,
|
|
40
|
+
type TradeInput,
|
|
41
|
+
type TradeResponse,
|
|
42
|
+
type Price,
|
|
43
|
+
type EmaPrice,
|
|
44
|
+
type PriceFeedResponse,
|
|
45
|
+
type OpenInterest,
|
|
46
|
+
type OpenInterestLimits,
|
|
47
|
+
type Utilization,
|
|
48
|
+
type Skew,
|
|
49
|
+
type Fee,
|
|
50
|
+
type Depth,
|
|
51
|
+
type LossProtectionInfo,
|
|
52
|
+
type PairData,
|
|
53
|
+
type Group,
|
|
54
|
+
type Snapshot,
|
|
55
|
+
type Trade,
|
|
56
|
+
type TradeInfo,
|
|
57
|
+
type OpenLimitOrder,
|
|
58
|
+
type ReferralTier,
|
|
59
|
+
type ReferralDiscount,
|
|
60
|
+
type ContractCallOptions,
|
|
61
|
+
type TransactionReceipt,
|
|
62
|
+
type PairsBackendReturn,
|
|
63
|
+
type ContractPairInfo,
|
|
64
|
+
type PairStruct,
|
|
65
|
+
type GroupStruct,
|
|
66
|
+
type FeeStruct,
|
|
67
|
+
type FeedStruct,
|
|
68
|
+
type BackupFeedStruct,
|
|
69
|
+
type LeverageStruct,
|
|
70
|
+
type ValuesStruct,
|
|
71
|
+
type PnlFeesStruct,
|
|
72
|
+
fromBlockchain10,
|
|
73
|
+
fromBlockchain6,
|
|
74
|
+
toBlockchain10,
|
|
75
|
+
toBlockchain6,
|
|
76
|
+
fromBlockchain12,
|
|
77
|
+
toBlockchain12,
|
|
78
|
+
fromBlockchain18,
|
|
79
|
+
toBlockchain18,
|
|
80
|
+
} from './types';
|
|
81
|
+
|
|
82
|
+
// Configuration
|
|
83
|
+
export {
|
|
84
|
+
CONTRACTS,
|
|
85
|
+
API_ENDPOINTS,
|
|
86
|
+
type ContractAddresses,
|
|
87
|
+
type NetworkConfig,
|
|
88
|
+
getContractAddress,
|
|
89
|
+
setContractAddresses,
|
|
90
|
+
} from './config';
|
|
91
|
+
|
|
92
|
+
// Utilities
|
|
93
|
+
export {
|
|
94
|
+
isTupleType,
|
|
95
|
+
isArrayType,
|
|
96
|
+
processOutputTypes,
|
|
97
|
+
decoder,
|
|
98
|
+
hexToNumber,
|
|
99
|
+
numberToHex,
|
|
100
|
+
isValidAddress,
|
|
101
|
+
toChecksumAddress,
|
|
102
|
+
sleep,
|
|
103
|
+
retryWithBackoff,
|
|
104
|
+
} from './utils';
|
|
105
|
+
|
|
106
|
+
// Crypto utilities (for advanced usage)
|
|
107
|
+
export {
|
|
108
|
+
publicKeyIntToEthAddress,
|
|
109
|
+
derEncodedPublicKeyToEthAddress,
|
|
110
|
+
getSigRS,
|
|
111
|
+
getSigV,
|
|
112
|
+
getSigRSV,
|
|
113
|
+
signatureToHex,
|
|
114
|
+
} from './crypto/spki';
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Contract, Provider } from 'ethers';
|
|
2
|
+
import {
|
|
3
|
+
OpenInterest,
|
|
4
|
+
OpenInterestLimits,
|
|
5
|
+
Utilization,
|
|
6
|
+
Skew,
|
|
7
|
+
TradeInput,
|
|
8
|
+
fromBlockchain6,
|
|
9
|
+
fromBlockchain10,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { PairsCache } from './pairs_cache';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RPC module for retrieving asset-level parameters
|
|
15
|
+
*/
|
|
16
|
+
export class AssetParametersRPC {
|
|
17
|
+
private provider: Provider;
|
|
18
|
+
private pairStorageContract: Contract;
|
|
19
|
+
private pairInfosContract: Contract;
|
|
20
|
+
private tradingStorageContract: Contract;
|
|
21
|
+
private pairsCache: PairsCache;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
provider: Provider,
|
|
25
|
+
pairStorageContract: Contract,
|
|
26
|
+
pairInfosContract: Contract,
|
|
27
|
+
pairsCache: PairsCache,
|
|
28
|
+
tradingStorageContract: Contract
|
|
29
|
+
) {
|
|
30
|
+
this.provider = provider;
|
|
31
|
+
this.pairStorageContract = pairStorageContract;
|
|
32
|
+
this.pairInfosContract = pairInfosContract;
|
|
33
|
+
this.tradingStorageContract = tradingStorageContract;
|
|
34
|
+
this.pairsCache = pairsCache;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get open interest limits for all pairs
|
|
39
|
+
* @returns Map of pair index to OI limits
|
|
40
|
+
*/
|
|
41
|
+
async getOILimits(): Promise<Map<number, OpenInterestLimits>> {
|
|
42
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
43
|
+
const limits = new Map<number, OpenInterestLimits>();
|
|
44
|
+
|
|
45
|
+
for (const [pairIndex, pairInfo] of pairs) {
|
|
46
|
+
limits.set(pairIndex, {
|
|
47
|
+
pairIndex,
|
|
48
|
+
maxLong: pairInfo.maxLongOiP,
|
|
49
|
+
maxShort: pairInfo.maxShortOiP,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return limits;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get current open interest for all pairs
|
|
58
|
+
* @returns Map of pair index to OI
|
|
59
|
+
*/
|
|
60
|
+
async getOI(): Promise<Map<number, OpenInterest>> {
|
|
61
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
62
|
+
const oi = new Map<number, OpenInterest>();
|
|
63
|
+
|
|
64
|
+
for (const [pairIndex] of pairs) {
|
|
65
|
+
try {
|
|
66
|
+
// Use TradingStorage contract which has openInterestUSDC method
|
|
67
|
+
const pairOILong = await this.tradingStorageContract.openInterestUSDC(pairIndex, 0); // 0 = long
|
|
68
|
+
const pairOIShort = await this.tradingStorageContract.openInterestUSDC(pairIndex, 1); // 1 = short
|
|
69
|
+
|
|
70
|
+
const limits = await this.getOILimits();
|
|
71
|
+
const maxOI = limits.get(pairIndex)?.maxLong || 0;
|
|
72
|
+
|
|
73
|
+
oi.set(pairIndex, {
|
|
74
|
+
long: fromBlockchain6(pairOILong),
|
|
75
|
+
short: fromBlockchain6(pairOIShort),
|
|
76
|
+
max: maxOI,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Error getting OI for pair ${pairIndex}:`, error);
|
|
80
|
+
// Set default values on error
|
|
81
|
+
oi.set(pairIndex, {
|
|
82
|
+
long: 0,
|
|
83
|
+
short: 0,
|
|
84
|
+
max: 0,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return oi;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get utilization for all pairs
|
|
94
|
+
* @returns Map of pair index to utilization
|
|
95
|
+
*/
|
|
96
|
+
async getUtilization(): Promise<Map<number, Utilization>> {
|
|
97
|
+
const oi = await this.getOI();
|
|
98
|
+
const utilization = new Map<number, Utilization>();
|
|
99
|
+
|
|
100
|
+
for (const [pairIndex, oiData] of oi) {
|
|
101
|
+
const utilizationLong = oiData.max > 0 ? (oiData.long / oiData.max) * 100 : 0;
|
|
102
|
+
const utilizationShort = oiData.max > 0 ? (oiData.short / oiData.max) * 100 : 0;
|
|
103
|
+
|
|
104
|
+
utilization.set(pairIndex, {
|
|
105
|
+
utilizationLong,
|
|
106
|
+
utilizationShort,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return utilization;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get asset skew (long / total) for all pairs
|
|
115
|
+
* @returns Map of pair index to skew
|
|
116
|
+
*/
|
|
117
|
+
async getAssetSkew(): Promise<Map<number, Skew>> {
|
|
118
|
+
const oi = await this.getOI();
|
|
119
|
+
const skew = new Map<number, Skew>();
|
|
120
|
+
|
|
121
|
+
for (const [pairIndex, oiData] of oi) {
|
|
122
|
+
const total = oiData.long + oiData.short;
|
|
123
|
+
const skewValue = total > 0 ? oiData.long / total : 0.5;
|
|
124
|
+
|
|
125
|
+
skew.set(pairIndex, { skew: skewValue });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return skew;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get price impact spread for opening a position
|
|
133
|
+
* @param positionSize - Position size in USDC
|
|
134
|
+
* @param isLong - True for long, false for short
|
|
135
|
+
* @param pairIndex - Pair index
|
|
136
|
+
* @returns Price impact spread percentage
|
|
137
|
+
*/
|
|
138
|
+
async getPriceImpactSpread(
|
|
139
|
+
positionSize: number,
|
|
140
|
+
isLong: boolean,
|
|
141
|
+
pairIndex: number
|
|
142
|
+
): Promise<number> {
|
|
143
|
+
try {
|
|
144
|
+
const result = await this.pairInfosContract.getPriceImpactP(
|
|
145
|
+
pairIndex,
|
|
146
|
+
isLong,
|
|
147
|
+
BigInt(Math.floor(positionSize * 1e6))
|
|
148
|
+
);
|
|
149
|
+
return fromBlockchain10(result);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Error getting price impact spread:', error);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get skew impact spread
|
|
158
|
+
* @param pairIndex - Pair index
|
|
159
|
+
* @returns Skew impact spread
|
|
160
|
+
*/
|
|
161
|
+
async getSkewImpactSpread(pairIndex: number): Promise<number> {
|
|
162
|
+
try {
|
|
163
|
+
const skewMap = await this.getAssetSkew();
|
|
164
|
+
const skew = skewMap.get(pairIndex);
|
|
165
|
+
|
|
166
|
+
if (!skew) return 0;
|
|
167
|
+
|
|
168
|
+
// Simple skew impact calculation (can be customized)
|
|
169
|
+
const deviation = Math.abs(skew.skew - 0.5);
|
|
170
|
+
return deviation * 100; // Convert to basis points
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Error getting skew impact spread:', error);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get opening price impact spread for a trade
|
|
179
|
+
* @param tradeInput - Trade input parameters
|
|
180
|
+
* @returns Opening price impact spread
|
|
181
|
+
*/
|
|
182
|
+
async getOpeningPriceImpactSpread(tradeInput: TradeInput): Promise<number> {
|
|
183
|
+
const pairIndex = await this.pairsCache.getPairIndex(tradeInput.pair);
|
|
184
|
+
if (pairIndex === undefined) {
|
|
185
|
+
throw new Error(`Pair ${tradeInput.pair} not found`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const positionSize = tradeInput.collateralInTrade * tradeInput.leverage;
|
|
189
|
+
return await this.getPriceImpactSpread(positionSize, tradeInput.isLong, pairIndex);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get one percent depth (liquidity depth)
|
|
194
|
+
* @returns Map of pair index to depth
|
|
195
|
+
*/
|
|
196
|
+
async getOnePercentDepth(): Promise<Map<number, { above: number; below: number }>> {
|
|
197
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
198
|
+
const depth = new Map<number, { above: number; below: number }>();
|
|
199
|
+
|
|
200
|
+
for (const [pairIndex] of pairs) {
|
|
201
|
+
try {
|
|
202
|
+
const depthAbove = await this.pairInfosContract.onePercentDepthAboveUsdc(pairIndex);
|
|
203
|
+
const depthBelow = await this.pairInfosContract.onePercentDepthBelowUsdc(pairIndex);
|
|
204
|
+
|
|
205
|
+
depth.set(pairIndex, {
|
|
206
|
+
above: fromBlockchain6(depthAbove),
|
|
207
|
+
below: fromBlockchain6(depthBelow),
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
// If method doesn't exist, set default values
|
|
211
|
+
depth.set(pairIndex, { above: 0, below: 0 });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return depth;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Contract, Provider } from 'ethers';
|
|
2
|
+
import { Utilization, Skew } from '../types';
|
|
3
|
+
import { AssetParametersRPC } from './asset_parameters';
|
|
4
|
+
import { CategoryParametersRPC } from './category_parameters';
|
|
5
|
+
import { PairsCache } from './pairs_cache';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* RPC module for blended calculations (25% asset + 75% category)
|
|
9
|
+
*/
|
|
10
|
+
export class BlendedRPC {
|
|
11
|
+
private assetParams: AssetParametersRPC;
|
|
12
|
+
private categoryParams: CategoryParametersRPC;
|
|
13
|
+
private pairsCache: PairsCache;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
assetParams: AssetParametersRPC,
|
|
17
|
+
categoryParams: CategoryParametersRPC,
|
|
18
|
+
pairsCache: PairsCache
|
|
19
|
+
) {
|
|
20
|
+
this.assetParams = assetParams;
|
|
21
|
+
this.categoryParams = categoryParams;
|
|
22
|
+
this.pairsCache = pairsCache;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate blended utilization (25% asset + 75% category)
|
|
27
|
+
* @returns Map of pair index to blended utilization
|
|
28
|
+
*/
|
|
29
|
+
async getBlendedUtilization(): Promise<Map<number, Utilization>> {
|
|
30
|
+
const assetUtilization = await this.assetParams.getUtilization();
|
|
31
|
+
const categoryUtilization = await this.categoryParams.getUtilization();
|
|
32
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
33
|
+
|
|
34
|
+
const blended = new Map<number, Utilization>();
|
|
35
|
+
|
|
36
|
+
for (const [pairIndex, pairInfo] of pairs) {
|
|
37
|
+
const assetUtil = assetUtilization.get(pairIndex);
|
|
38
|
+
const catUtil = categoryUtilization.get(pairInfo.groupIndex);
|
|
39
|
+
|
|
40
|
+
if (assetUtil && catUtil) {
|
|
41
|
+
blended.set(pairIndex, {
|
|
42
|
+
utilizationLong:
|
|
43
|
+
0.25 * assetUtil.utilizationLong + 0.75 * catUtil.utilizationLong,
|
|
44
|
+
utilizationShort:
|
|
45
|
+
0.25 * assetUtil.utilizationShort + 0.75 * catUtil.utilizationShort,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return blended;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Calculate blended skew (25% asset + 75% category)
|
|
55
|
+
* @returns Map of pair index to blended skew
|
|
56
|
+
*/
|
|
57
|
+
async getBlendedSkew(): Promise<Map<number, Skew>> {
|
|
58
|
+
const assetSkew = await this.assetParams.getAssetSkew();
|
|
59
|
+
const categorySkew = await this.categoryParams.getCategorySkew();
|
|
60
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
61
|
+
|
|
62
|
+
const blended = new Map<number, Skew>();
|
|
63
|
+
|
|
64
|
+
for (const [pairIndex, pairInfo] of pairs) {
|
|
65
|
+
const assetSk = assetSkew.get(pairIndex);
|
|
66
|
+
const catSk = categorySkew.get(pairInfo.groupIndex);
|
|
67
|
+
|
|
68
|
+
if (assetSk && catSk) {
|
|
69
|
+
blended.set(pairIndex, {
|
|
70
|
+
skew: 0.25 * assetSk.skew + 0.75 * catSk.skew,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return blended;
|
|
76
|
+
}
|
|
77
|
+
}
|