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,128 @@
|
|
|
1
|
+
import { Contract, Provider } from 'ethers';
|
|
2
|
+
import { OpenInterest, Utilization, Skew, fromBlockchain6 } from '../types';
|
|
3
|
+
import { PairsCache } from './pairs_cache';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RPC module for retrieving category-level parameters
|
|
7
|
+
*/
|
|
8
|
+
export class CategoryParametersRPC {
|
|
9
|
+
private provider: Provider;
|
|
10
|
+
private pairStorageContract: Contract;
|
|
11
|
+
private pairsCache: PairsCache;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
provider: Provider,
|
|
15
|
+
pairStorageContract: Contract,
|
|
16
|
+
pairsCache: PairsCache
|
|
17
|
+
) {
|
|
18
|
+
this.provider = provider;
|
|
19
|
+
this.pairStorageContract = pairStorageContract;
|
|
20
|
+
this.pairsCache = pairsCache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get open interest limits per category
|
|
25
|
+
* Note: Group OI limits are calculated from pair backend data
|
|
26
|
+
* @returns Map of group index to OI limits
|
|
27
|
+
*/
|
|
28
|
+
async getOILimits(): Promise<Map<number, OpenInterest>> {
|
|
29
|
+
const groupIndexes = await this.pairsCache.getGroupIndexes();
|
|
30
|
+
const limits = new Map<number, OpenInterest>();
|
|
31
|
+
|
|
32
|
+
// Get limits from pair backend data which includes group info
|
|
33
|
+
for (const groupIndex of groupIndexes) {
|
|
34
|
+
try {
|
|
35
|
+
// Get first pair in this group to get group max OI
|
|
36
|
+
const pairsInGroup = await this.pairsCache.getPairsInGroup(groupIndex);
|
|
37
|
+
if (pairsInGroup.length > 0) {
|
|
38
|
+
const backendData = await this.pairsCache.getPairBackend(pairsInGroup[0]);
|
|
39
|
+
const maxOI = Number(backendData.group.maxOpenInterestP) / 1e10; // Convert from 10 decimals to number
|
|
40
|
+
|
|
41
|
+
limits.set(groupIndex, {
|
|
42
|
+
long: maxOI,
|
|
43
|
+
short: maxOI,
|
|
44
|
+
max: maxOI,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Error getting OI limits for group ${groupIndex}:`, error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return limits;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get current open interest per category
|
|
57
|
+
* Note: Calculated by summing pair OIs in each group
|
|
58
|
+
* @returns Map of group index to OI
|
|
59
|
+
*/
|
|
60
|
+
async getOI(): Promise<Map<number, OpenInterest>> {
|
|
61
|
+
const groupIndexes = await this.pairsCache.getGroupIndexes();
|
|
62
|
+
const oi = new Map<number, OpenInterest>();
|
|
63
|
+
const limits = await this.getOILimits();
|
|
64
|
+
|
|
65
|
+
for (const groupIndex of groupIndexes) {
|
|
66
|
+
try {
|
|
67
|
+
// Sum up OI from all pairs in this group
|
|
68
|
+
const pairsInGroup = await this.pairsCache.getPairsInGroup(groupIndex);
|
|
69
|
+
let totalLongOI = 0;
|
|
70
|
+
let totalShortOI = 0;
|
|
71
|
+
|
|
72
|
+
// This would require asset params to get individual pair OI
|
|
73
|
+
// For now, return default values
|
|
74
|
+
// TODO: Properly calculate by summing pair OIs
|
|
75
|
+
const maxOI = limits.get(groupIndex)?.max || 0;
|
|
76
|
+
|
|
77
|
+
oi.set(groupIndex, {
|
|
78
|
+
long: totalLongOI,
|
|
79
|
+
short: totalShortOI,
|
|
80
|
+
max: maxOI,
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Error getting OI for group ${groupIndex}:`, error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return oi;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get utilization per category
|
|
92
|
+
* @returns Map of group index to utilization
|
|
93
|
+
*/
|
|
94
|
+
async getUtilization(): Promise<Map<number, Utilization>> {
|
|
95
|
+
const oi = await this.getOI();
|
|
96
|
+
const utilization = new Map<number, Utilization>();
|
|
97
|
+
|
|
98
|
+
for (const [groupIndex, oiData] of oi) {
|
|
99
|
+
const utilizationLong = oiData.max > 0 ? (oiData.long / oiData.max) * 100 : 0;
|
|
100
|
+
const utilizationShort = oiData.max > 0 ? (oiData.short / oiData.max) * 100 : 0;
|
|
101
|
+
|
|
102
|
+
utilization.set(groupIndex, {
|
|
103
|
+
utilizationLong,
|
|
104
|
+
utilizationShort,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return utilization;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get category skew (long / total)
|
|
113
|
+
* @returns Map of group index to skew
|
|
114
|
+
*/
|
|
115
|
+
async getCategorySkew(): Promise<Map<number, Skew>> {
|
|
116
|
+
const oi = await this.getOI();
|
|
117
|
+
const skew = new Map<number, Skew>();
|
|
118
|
+
|
|
119
|
+
for (const [groupIndex, oiData] of oi) {
|
|
120
|
+
const total = oiData.long + oiData.short;
|
|
121
|
+
const skewValue = total > 0 ? oiData.long / total : 0.5;
|
|
122
|
+
|
|
123
|
+
skew.set(groupIndex, { skew: skewValue });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return skew;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { Contract, TransactionReceipt, TransactionRequest } from 'ethers';
|
|
2
|
+
import { BaseSigner } from '../signers/base';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delegation RPC
|
|
6
|
+
* Handles delegation functionality for the Trading contract.
|
|
7
|
+
* Allows one address to execute trades on behalf of another.
|
|
8
|
+
*/
|
|
9
|
+
export class DelegationRPC {
|
|
10
|
+
constructor(
|
|
11
|
+
private tradingContract: Contract,
|
|
12
|
+
private signer?: BaseSigner
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set a delegate wallet that can perform trade actions on behalf of caller
|
|
17
|
+
* @param delegate - Address to authorize as delegate (use 0x0 to revoke)
|
|
18
|
+
* @returns Transaction receipt
|
|
19
|
+
*/
|
|
20
|
+
async setDelegate(delegate: string): Promise<TransactionReceipt | null> {
|
|
21
|
+
if (!this.signer) {
|
|
22
|
+
throw new Error('Signer required for delegation operations');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tx: TransactionRequest = {
|
|
26
|
+
to: await this.tradingContract.getAddress(),
|
|
27
|
+
data: this.tradingContract.interface.encodeFunctionData('setDelegate', [delegate]),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return await this.signAndSend(tx);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Remove the current delegate for the caller
|
|
35
|
+
* @returns Transaction receipt
|
|
36
|
+
*/
|
|
37
|
+
async removeDelegate(): Promise<TransactionReceipt | null> {
|
|
38
|
+
if (!this.signer) {
|
|
39
|
+
throw new Error('Signer required for delegation operations');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tx: TransactionRequest = {
|
|
43
|
+
to: await this.tradingContract.getAddress(),
|
|
44
|
+
data: this.tradingContract.interface.encodeFunctionData('removeDelegate'),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return await this.signAndSend(tx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the delegate wallet for a given owner
|
|
52
|
+
* @param owner - Owner address to check
|
|
53
|
+
* @returns Delegate address (0x0 if none set)
|
|
54
|
+
*/
|
|
55
|
+
async getDelegateFor(owner: string): Promise<string> {
|
|
56
|
+
return await this.tradingContract.delegations(owner);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Execute a delegated action on behalf of a trader
|
|
61
|
+
* @param trader - Address of the trader to act on behalf of
|
|
62
|
+
* @param callData - ABI-encoded function data for the action
|
|
63
|
+
* @param value - ETH value to send (for execution fees)
|
|
64
|
+
* @returns Transaction receipt
|
|
65
|
+
*/
|
|
66
|
+
async delegatedAction(
|
|
67
|
+
trader: string,
|
|
68
|
+
callData: string,
|
|
69
|
+
value: bigint = 0n
|
|
70
|
+
): Promise<TransactionReceipt | null> {
|
|
71
|
+
if (!this.signer) {
|
|
72
|
+
throw new Error('Signer required for delegation operations');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const tx: TransactionRequest = {
|
|
76
|
+
to: await this.tradingContract.getAddress(),
|
|
77
|
+
data: this.tradingContract.interface.encodeFunctionData('delegatedAction', [
|
|
78
|
+
trader,
|
|
79
|
+
callData,
|
|
80
|
+
]),
|
|
81
|
+
value,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return await this.signAndSend(tx);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Helper: Create call data for openTrade to be used with delegatedAction
|
|
89
|
+
* @param tradeStruct - Trade struct with blockchain values
|
|
90
|
+
* @param orderType - Order type (0-3)
|
|
91
|
+
* @param slippageP - Slippage percentage in blockchain units
|
|
92
|
+
* @returns Encoded call data
|
|
93
|
+
*/
|
|
94
|
+
encodeOpenTrade(
|
|
95
|
+
tradeStruct: any,
|
|
96
|
+
orderType: number,
|
|
97
|
+
slippageP: bigint
|
|
98
|
+
): string {
|
|
99
|
+
return this.tradingContract.interface.encodeFunctionData('openTrade', [
|
|
100
|
+
tradeStruct,
|
|
101
|
+
orderType,
|
|
102
|
+
slippageP,
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Helper: Create call data for closeTradeMarket to be used with delegatedAction
|
|
108
|
+
* @param pairIndex - Trading pair index
|
|
109
|
+
* @param index - Trade index
|
|
110
|
+
* @param amount - Amount to close in blockchain units
|
|
111
|
+
* @returns Encoded call data
|
|
112
|
+
*/
|
|
113
|
+
encodeCloseTradeMarket(
|
|
114
|
+
pairIndex: number,
|
|
115
|
+
index: number,
|
|
116
|
+
amount: bigint
|
|
117
|
+
): string {
|
|
118
|
+
return this.tradingContract.interface.encodeFunctionData('closeTradeMarket', [
|
|
119
|
+
pairIndex,
|
|
120
|
+
index,
|
|
121
|
+
amount,
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Helper: Create call data for updateTpAndSl to be used with delegatedAction
|
|
127
|
+
* @param pairIndex - Trading pair index
|
|
128
|
+
* @param index - Trade index
|
|
129
|
+
* @param newSl - New stop loss in blockchain units
|
|
130
|
+
* @param newTp - New take profit in blockchain units
|
|
131
|
+
* @param priceUpdateData - Price update data
|
|
132
|
+
* @returns Encoded call data
|
|
133
|
+
*/
|
|
134
|
+
encodeUpdateTpAndSl(
|
|
135
|
+
pairIndex: number,
|
|
136
|
+
index: number,
|
|
137
|
+
newSl: bigint,
|
|
138
|
+
newTp: bigint,
|
|
139
|
+
priceUpdateData: string[]
|
|
140
|
+
): string {
|
|
141
|
+
return this.tradingContract.interface.encodeFunctionData('updateTpAndSl', [
|
|
142
|
+
pairIndex,
|
|
143
|
+
index,
|
|
144
|
+
newSl,
|
|
145
|
+
newTp,
|
|
146
|
+
priceUpdateData,
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Helper: Create call data for updateMargin to be used with delegatedAction
|
|
152
|
+
* @param pairIndex - Trading pair index
|
|
153
|
+
* @param index - Trade index
|
|
154
|
+
* @param updateType - 0 for DEPOSIT, 1 for WITHDRAW
|
|
155
|
+
* @param amount - Amount in blockchain units
|
|
156
|
+
* @param priceUpdateData - Price update data
|
|
157
|
+
* @returns Encoded call data
|
|
158
|
+
*/
|
|
159
|
+
encodeUpdateMargin(
|
|
160
|
+
pairIndex: number,
|
|
161
|
+
index: number,
|
|
162
|
+
updateType: number,
|
|
163
|
+
amount: bigint,
|
|
164
|
+
priceUpdateData: string[]
|
|
165
|
+
): string {
|
|
166
|
+
return this.tradingContract.interface.encodeFunctionData('updateMargin', [
|
|
167
|
+
pairIndex,
|
|
168
|
+
index,
|
|
169
|
+
updateType,
|
|
170
|
+
amount,
|
|
171
|
+
priceUpdateData,
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Helper method to sign and send transactions
|
|
177
|
+
*/
|
|
178
|
+
private async signAndSend(tx: TransactionRequest): Promise<TransactionReceipt | null> {
|
|
179
|
+
if (!this.signer) {
|
|
180
|
+
throw new Error('Signer not set');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const address = await this.signer.getAddress();
|
|
184
|
+
tx.from = address;
|
|
185
|
+
|
|
186
|
+
const provider = this.tradingContract.runner?.provider;
|
|
187
|
+
if (!provider) {
|
|
188
|
+
throw new Error('Provider not available');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!tx.chainId) {
|
|
192
|
+
const network = await provider.getNetwork();
|
|
193
|
+
tx.chainId = network.chainId;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (tx.nonce === undefined) {
|
|
197
|
+
tx.nonce = await provider.getTransactionCount(address);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!tx.gasLimit) {
|
|
201
|
+
tx.gasLimit = await provider.estimateGas(tx);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!tx.maxFeePerGas && !tx.gasPrice) {
|
|
205
|
+
const feeData = await provider.getFeeData();
|
|
206
|
+
if (feeData.maxFeePerGas) {
|
|
207
|
+
tx.maxFeePerGas = feeData.maxFeePerGas;
|
|
208
|
+
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || feeData.maxFeePerGas;
|
|
209
|
+
} else {
|
|
210
|
+
tx.gasPrice = feeData.gasPrice || undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const signedTx = await this.signer.signTransaction(tx);
|
|
215
|
+
const txResponse = await provider.broadcastTransaction(signedTx);
|
|
216
|
+
return await txResponse.wait();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Set signer for transactions
|
|
221
|
+
*/
|
|
222
|
+
setSigner(signer: BaseSigner): void {
|
|
223
|
+
this.signer = signer;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Contract, Provider } from 'ethers';
|
|
2
|
+
import { Fee, TradeInput, fromBlockchain10, toBlockchain6 } from '../types';
|
|
3
|
+
import { PairsCache } from './pairs_cache';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RPC module for fee calculations
|
|
7
|
+
*/
|
|
8
|
+
export class FeeParametersRPC {
|
|
9
|
+
private provider: Provider;
|
|
10
|
+
private pairInfosContract: Contract;
|
|
11
|
+
private referralContract?: Contract;
|
|
12
|
+
private pairsCache: PairsCache;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
provider: Provider,
|
|
16
|
+
pairInfosContract: Contract,
|
|
17
|
+
pairsCache: PairsCache,
|
|
18
|
+
referralContract?: Contract
|
|
19
|
+
) {
|
|
20
|
+
this.provider = provider;
|
|
21
|
+
this.pairInfosContract = pairInfosContract;
|
|
22
|
+
this.pairsCache = pairsCache;
|
|
23
|
+
this.referralContract = referralContract;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get margin fee for all pairs (in basis points)
|
|
28
|
+
* @returns Map of pair index to fee
|
|
29
|
+
*/
|
|
30
|
+
async getMarginFee(): Promise<Map<number, Fee>> {
|
|
31
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
32
|
+
const fees = new Map<number, Fee>();
|
|
33
|
+
|
|
34
|
+
for (const [pairIndex] of pairs) {
|
|
35
|
+
try {
|
|
36
|
+
const feeP = await this.pairInfosContract.getPairMarginFeeP(pairIndex);
|
|
37
|
+
fees.set(pairIndex, {
|
|
38
|
+
feeP: fromBlockchain10(feeP),
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`Error getting margin fee for pair ${pairIndex}:`, error);
|
|
42
|
+
fees.set(pairIndex, { feeP: 0 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fees;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get constant spread parameter for each pair
|
|
51
|
+
* @returns Map of pair index to constant spread
|
|
52
|
+
*/
|
|
53
|
+
async constantSpreadParameter(): Promise<Map<number, number>> {
|
|
54
|
+
const pairs = await this.pairsCache.getPairsInfo();
|
|
55
|
+
const spreads = new Map<number, number>();
|
|
56
|
+
|
|
57
|
+
for (const [pairIndex, pairInfo] of pairs) {
|
|
58
|
+
// Use the min spread as the constant spread
|
|
59
|
+
spreads.set(pairIndex, pairInfo.spread.min);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return spreads;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get opening fee for a position
|
|
67
|
+
* @param positionSize - Position size in USDC
|
|
68
|
+
* @param isLong - True for long, false for short
|
|
69
|
+
* @param pairIndex - Pair index
|
|
70
|
+
* @returns Opening fee in USDC
|
|
71
|
+
*/
|
|
72
|
+
async getOpeningFee(
|
|
73
|
+
positionSize: number,
|
|
74
|
+
isLong: boolean,
|
|
75
|
+
pairIndex: number
|
|
76
|
+
): Promise<number> {
|
|
77
|
+
try {
|
|
78
|
+
const feeUsdc = await this.pairInfosContract.getOpenFeeUsdc(
|
|
79
|
+
pairIndex,
|
|
80
|
+
toBlockchain6(positionSize),
|
|
81
|
+
isLong
|
|
82
|
+
);
|
|
83
|
+
return fromBlockchain10(feeUsdc);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Error getting opening fee:', error);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get opening fee for a new trade with referral
|
|
92
|
+
* @param tradeInput - Trade input parameters
|
|
93
|
+
* @param referrer - Referrer address (optional)
|
|
94
|
+
* @returns Opening fee in USDC
|
|
95
|
+
*/
|
|
96
|
+
async getNewTradeOpeningFee(
|
|
97
|
+
tradeInput: TradeInput,
|
|
98
|
+
referrer?: string
|
|
99
|
+
): Promise<number> {
|
|
100
|
+
const pairIndex = await this.pairsCache.getPairIndex(tradeInput.pair);
|
|
101
|
+
if (pairIndex === undefined) {
|
|
102
|
+
throw new Error(`Pair ${tradeInput.pair} not found`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const positionSize = tradeInput.collateralInTrade * tradeInput.leverage;
|
|
106
|
+
let fee = await this.getOpeningFee(positionSize, tradeInput.isLong, pairIndex);
|
|
107
|
+
|
|
108
|
+
// Apply referral discount if applicable
|
|
109
|
+
if (referrer && this.referralContract) {
|
|
110
|
+
try {
|
|
111
|
+
const rebate = await this.getTradeReferralRebate(
|
|
112
|
+
tradeInput.referrer || referrer,
|
|
113
|
+
referrer,
|
|
114
|
+
fee
|
|
115
|
+
);
|
|
116
|
+
fee -= rebate;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Error getting referral rebate:', error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return fee;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get trade referral rebate
|
|
127
|
+
* @param trader - Trader address
|
|
128
|
+
* @param referrer - Referrer address
|
|
129
|
+
* @param openingFee - Opening fee amount
|
|
130
|
+
* @returns Rebate amount
|
|
131
|
+
*/
|
|
132
|
+
async getTradeReferralRebate(
|
|
133
|
+
trader: string,
|
|
134
|
+
referrer: string,
|
|
135
|
+
openingFee: number
|
|
136
|
+
): Promise<number> {
|
|
137
|
+
if (!this.referralContract) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const rebateP = await this.referralContract.getReferralRebateP(trader, referrer);
|
|
143
|
+
const rebatePercentage = fromBlockchain10(rebateP);
|
|
144
|
+
return openingFee * (rebatePercentage / 100);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Error getting referral rebate:', error);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Contract } from 'ethers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Call structure for multicall
|
|
5
|
+
*/
|
|
6
|
+
export interface MulticallCall {
|
|
7
|
+
target: string;
|
|
8
|
+
callData: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Result structure from multicall
|
|
13
|
+
*/
|
|
14
|
+
export interface MulticallResult {
|
|
15
|
+
blockNumber: number;
|
|
16
|
+
returnData: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Multicall RPC
|
|
21
|
+
* Allows batching multiple contract view calls into a single request
|
|
22
|
+
*/
|
|
23
|
+
export class MulticallRPC {
|
|
24
|
+
constructor(private multicallContract: Contract) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute multiple calls in a single transaction
|
|
28
|
+
* @param calls - Array of calls to execute
|
|
29
|
+
* @returns Block number and return data from each call
|
|
30
|
+
*/
|
|
31
|
+
async aggregate(calls: MulticallCall[]): Promise<MulticallResult> {
|
|
32
|
+
const result = await this.multicallContract.aggregate(calls);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
blockNumber: Number(result.blockNumber || result[0]),
|
|
36
|
+
returnData: Array.isArray(result.returnData) ? result.returnData : result[1],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Helper: Create call data for a contract function
|
|
42
|
+
* @param contract - Contract instance
|
|
43
|
+
* @param functionName - Function name
|
|
44
|
+
* @param args - Function arguments
|
|
45
|
+
* @returns Call data object
|
|
46
|
+
*/
|
|
47
|
+
createCall(contract: Contract, functionName: string, args: any[] = []): MulticallCall {
|
|
48
|
+
return {
|
|
49
|
+
target: contract.target as string,
|
|
50
|
+
callData: contract.interface.encodeFunctionData(functionName, args),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper: Decode return data from a multicall result
|
|
56
|
+
* @param contract - Contract instance used for decoding
|
|
57
|
+
* @param functionName - Function name that was called
|
|
58
|
+
* @param returnData - Raw return data from multicall
|
|
59
|
+
* @returns Decoded result
|
|
60
|
+
*/
|
|
61
|
+
decodeResult(contract: Contract, functionName: string, returnData: string): any {
|
|
62
|
+
return contract.interface.decodeFunctionResult(functionName, returnData);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Execute multicall and decode results
|
|
67
|
+
* @param calls - Array of call definitions with contract and function info
|
|
68
|
+
* @returns Decoded results
|
|
69
|
+
*/
|
|
70
|
+
async aggregateAndDecode(
|
|
71
|
+
calls: Array<{
|
|
72
|
+
contract: Contract;
|
|
73
|
+
functionName: string;
|
|
74
|
+
args?: any[];
|
|
75
|
+
}>
|
|
76
|
+
): Promise<{
|
|
77
|
+
blockNumber: number;
|
|
78
|
+
results: any[];
|
|
79
|
+
}> {
|
|
80
|
+
// Create multicall calls
|
|
81
|
+
const multicallCalls = calls.map((call) =>
|
|
82
|
+
this.createCall(call.contract, call.functionName, call.args || [])
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Execute multicall
|
|
86
|
+
const { blockNumber, returnData } = await this.aggregate(multicallCalls);
|
|
87
|
+
|
|
88
|
+
// Decode results
|
|
89
|
+
const results = returnData.map((data, index) => {
|
|
90
|
+
const call = calls[index];
|
|
91
|
+
return this.decodeResult(call.contract, call.functionName, data);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
blockNumber,
|
|
96
|
+
results,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Batch read multiple open trades
|
|
102
|
+
* @param tradingStorageContract - TradingStorage contract instance
|
|
103
|
+
* @param trader - Trader address
|
|
104
|
+
* @param pairIndex - Pair index
|
|
105
|
+
* @param indices - Array of trade indices
|
|
106
|
+
* @returns Array of decoded trade data
|
|
107
|
+
*/
|
|
108
|
+
async batchGetOpenTrades(
|
|
109
|
+
tradingStorageContract: Contract,
|
|
110
|
+
trader: string,
|
|
111
|
+
pairIndex: number,
|
|
112
|
+
indices: number[]
|
|
113
|
+
): Promise<any[]> {
|
|
114
|
+
const calls = indices.map((index) => ({
|
|
115
|
+
contract: tradingStorageContract,
|
|
116
|
+
functionName: 'openTrades',
|
|
117
|
+
args: [trader, pairIndex, index],
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const { results } = await this.aggregateAndDecode(calls);
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Batch read multiple trade infos
|
|
126
|
+
* @param tradingStorageContract - TradingStorage contract instance
|
|
127
|
+
* @param trader - Trader address
|
|
128
|
+
* @param pairIndex - Pair index
|
|
129
|
+
* @param indices - Array of trade indices
|
|
130
|
+
* @returns Array of decoded trade info data
|
|
131
|
+
*/
|
|
132
|
+
async batchGetOpenTradesInfo(
|
|
133
|
+
tradingStorageContract: Contract,
|
|
134
|
+
trader: string,
|
|
135
|
+
pairIndex: number,
|
|
136
|
+
indices: number[]
|
|
137
|
+
): Promise<any[]> {
|
|
138
|
+
const calls = indices.map((index) => ({
|
|
139
|
+
contract: tradingStorageContract,
|
|
140
|
+
functionName: 'openTradesInfo',
|
|
141
|
+
args: [trader, pairIndex, index],
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
const { results } = await this.aggregateAndDecode(calls);
|
|
145
|
+
return results;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Batch read pair information
|
|
150
|
+
* @param pairStorageContract - PairStorage contract instance
|
|
151
|
+
* @param pairIndices - Array of pair indices
|
|
152
|
+
* @returns Array of decoded pair data
|
|
153
|
+
*/
|
|
154
|
+
async batchGetPairs(pairStorageContract: Contract, pairIndices: number[]): Promise<any[]> {
|
|
155
|
+
const calls = pairIndices.map((pairIndex) => ({
|
|
156
|
+
contract: pairStorageContract,
|
|
157
|
+
functionName: 'pairs',
|
|
158
|
+
args: [pairIndex],
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const { results } = await this.aggregateAndDecode(calls);
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
}
|