@strobelabs/perpcity-sdk 0.5.1 → 0.6.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/dist/{index.mjs → index.cjs} +548 -65
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +101 -8
- package/dist/index.d.ts +101 -8
- package/dist/index.js +433 -158
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/dist/index.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
BEACON_ABI: () => BEACON_ABI,
|
|
34
|
-
BIGINT_1E6: () => BIGINT_1E6,
|
|
35
|
-
ContractError: () => ContractError,
|
|
36
|
-
ErrorCategory: () => ErrorCategory,
|
|
37
|
-
ErrorSource: () => ErrorSource,
|
|
38
|
-
InsufficientFundsError: () => InsufficientFundsError,
|
|
39
|
-
MAX_PRICE: () => MAX_PRICE,
|
|
40
|
-
MAX_TICK: () => MAX_TICK,
|
|
41
|
-
MIN_PRICE: () => MIN_PRICE,
|
|
42
|
-
MIN_TICK: () => MIN_TICK,
|
|
43
|
-
NUMBER_1E6: () => NUMBER_1E6,
|
|
44
|
-
OpenPosition: () => OpenPosition,
|
|
45
|
-
PERP_MANAGER_ABI: () => PERP_MANAGER_ABI,
|
|
46
|
-
PerpCityContext: () => PerpCityContext,
|
|
47
|
-
PerpCityError: () => PerpCityError,
|
|
48
|
-
Q96: () => Q96,
|
|
49
|
-
RPCError: () => RPCError,
|
|
50
|
-
TransactionRejectedError: () => TransactionRejectedError,
|
|
51
|
-
ValidationError: () => ValidationError,
|
|
52
|
-
approveUsdc: () => approveUsdc,
|
|
53
|
-
calculateAlignedTicks: () => calculateAlignedTicks,
|
|
54
|
-
calculateEntryPrice: () => calculateEntryPrice,
|
|
55
|
-
calculateLeverage: () => calculateLeverage,
|
|
56
|
-
calculateLiquidationPrice: () => calculateLiquidationPrice,
|
|
57
|
-
calculateLiquidityForTargetRatio: () => calculateLiquidityForTargetRatio,
|
|
58
|
-
calculatePositionSize: () => calculatePositionSize,
|
|
59
|
-
calculatePositionValue: () => calculatePositionValue,
|
|
60
|
-
closePosition: () => closePosition,
|
|
61
|
-
createPerp: () => createPerp,
|
|
62
|
-
estimateLiquidity: () => estimateLiquidity,
|
|
63
|
-
getPerpBeacon: () => getPerpBeacon,
|
|
64
|
-
getPerpBounds: () => getPerpBounds,
|
|
65
|
-
getPerpFees: () => getPerpFees,
|
|
66
|
-
getPerpMark: () => getPerpMark,
|
|
67
|
-
getPerpTickSpacing: () => getPerpTickSpacing,
|
|
68
|
-
getPositionEffectiveMargin: () => getPositionEffectiveMargin,
|
|
69
|
-
getPositionFundingPayment: () => getPositionFundingPayment,
|
|
70
|
-
getPositionId: () => getPositionId,
|
|
71
|
-
getPositionIsLiquidatable: () => getPositionIsLiquidatable,
|
|
72
|
-
getPositionIsLong: () => getPositionIsLong,
|
|
73
|
-
getPositionIsMaker: () => getPositionIsMaker,
|
|
74
|
-
getPositionLiveDetails: () => getPositionLiveDetails,
|
|
75
|
-
getPositionLiveDetailsFromContract: () => getPositionLiveDetailsFromContract,
|
|
76
|
-
getPositionPerpId: () => getPositionPerpId,
|
|
77
|
-
getPositionPnl: () => getPositionPnl,
|
|
78
|
-
getRpcUrl: () => getRpcUrl,
|
|
79
|
-
getUserOpenPositions: () => getUserOpenPositions,
|
|
80
|
-
getUserUsdcBalance: () => getUserUsdcBalance,
|
|
81
|
-
getUserWalletAddress: () => getUserWalletAddress,
|
|
82
|
-
marginRatioToLeverage: () => marginRatioToLeverage,
|
|
83
|
-
openMakerPosition: () => openMakerPosition,
|
|
84
|
-
openTakerPosition: () => openTakerPosition,
|
|
85
|
-
parseContractError: () => parseContractError,
|
|
86
|
-
priceToSqrtPriceX96: () => priceToSqrtPriceX96,
|
|
87
|
-
priceToTick: () => priceToTick,
|
|
88
|
-
quoteOpenMakerPosition: () => quoteOpenMakerPosition,
|
|
89
|
-
scale6Decimals: () => scale6Decimals,
|
|
90
|
-
scaleFrom6Decimals: () => scaleFrom6Decimals,
|
|
91
|
-
scaleFromX96: () => scaleFromX96,
|
|
92
|
-
scaleToX96: () => scaleToX96,
|
|
93
|
-
sqrtPriceX96ToPrice: () => sqrtPriceX96ToPrice,
|
|
94
|
-
tickToPrice: () => tickToPrice,
|
|
95
|
-
withErrorHandling: () => withErrorHandling
|
|
96
|
-
});
|
|
97
|
-
module.exports = __toCommonJS(index_exports);
|
|
98
|
-
|
|
99
1
|
// src/abis/beacon.ts
|
|
100
2
|
var BEACON_ABI = [
|
|
101
3
|
{
|
|
@@ -1875,19 +1777,14 @@ var PERP_MANAGER_ABI = [
|
|
|
1875
1777
|
type: "int256"
|
|
1876
1778
|
},
|
|
1877
1779
|
{
|
|
1878
|
-
internalType: "
|
|
1780
|
+
internalType: "uint256",
|
|
1879
1781
|
name: "netMargin",
|
|
1880
|
-
type: "
|
|
1782
|
+
type: "uint256"
|
|
1881
1783
|
},
|
|
1882
1784
|
{
|
|
1883
1785
|
internalType: "bool",
|
|
1884
1786
|
name: "wasLiquidated",
|
|
1885
1787
|
type: "bool"
|
|
1886
|
-
},
|
|
1887
|
-
{
|
|
1888
|
-
internalType: "uint256",
|
|
1889
|
-
name: "notional",
|
|
1890
|
-
type: "uint256"
|
|
1891
1788
|
}
|
|
1892
1789
|
],
|
|
1893
1790
|
stateMutability: "nonpayable",
|
|
@@ -2364,8 +2261,13 @@ var PERP_MANAGER_ABI = [
|
|
|
2364
2261
|
];
|
|
2365
2262
|
|
|
2366
2263
|
// src/context.ts
|
|
2367
|
-
|
|
2368
|
-
|
|
2264
|
+
import TTLCache from "@isaacs/ttlcache";
|
|
2265
|
+
import {
|
|
2266
|
+
createPublicClient,
|
|
2267
|
+
erc20Abi as erc20Abi2,
|
|
2268
|
+
formatUnits,
|
|
2269
|
+
http
|
|
2270
|
+
} from "viem";
|
|
2369
2271
|
|
|
2370
2272
|
// src/abis/fees.ts
|
|
2371
2273
|
var FEES_ABI = [
|
|
@@ -2446,13 +2348,22 @@ var MARGIN_RATIOS_ABI = [
|
|
|
2446
2348
|
];
|
|
2447
2349
|
|
|
2448
2350
|
// src/utils/approve.ts
|
|
2449
|
-
|
|
2351
|
+
import { erc20Abi } from "viem";
|
|
2450
2352
|
var DEFAULT_CONFIRMATIONS = 2;
|
|
2353
|
+
async function getUsdcAllowance(context, owner) {
|
|
2354
|
+
const deployments = context.deployments();
|
|
2355
|
+
return context.publicClient.readContract({
|
|
2356
|
+
address: deployments.usdc,
|
|
2357
|
+
abi: erc20Abi,
|
|
2358
|
+
functionName: "allowance",
|
|
2359
|
+
args: [owner, deployments.perpManager]
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2451
2362
|
async function approveUsdc(context, amount, confirmations = DEFAULT_CONFIRMATIONS) {
|
|
2452
2363
|
const deployments = context.deployments();
|
|
2453
2364
|
const { request } = await context.publicClient.simulateContract({
|
|
2454
2365
|
address: deployments.usdc,
|
|
2455
|
-
abi:
|
|
2366
|
+
abi: erc20Abi,
|
|
2456
2367
|
functionName: "approve",
|
|
2457
2368
|
args: [deployments.perpManager, amount],
|
|
2458
2369
|
account: context.walletClient.account
|
|
@@ -2472,6 +2383,8 @@ var MIN_TICK = -887272;
|
|
|
2472
2383
|
var MAX_TICK = 887272;
|
|
2473
2384
|
var MIN_PRICE = 1e-6;
|
|
2474
2385
|
var MAX_PRICE = 1e6;
|
|
2386
|
+
var UINT256_MAX = (1n << 256n) - 1n;
|
|
2387
|
+
var INT256_THRESHOLD = 1n << 255n;
|
|
2475
2388
|
|
|
2476
2389
|
// src/utils/conversions.ts
|
|
2477
2390
|
function priceToSqrtPriceX96(price) {
|
|
@@ -2515,6 +2428,12 @@ function priceToTick(price, roundDown) {
|
|
|
2515
2428
|
function tickToPrice(tick) {
|
|
2516
2429
|
return 1.0001 ** tick;
|
|
2517
2430
|
}
|
|
2431
|
+
function uint256ToInt256(value) {
|
|
2432
|
+
if (value >= INT256_THRESHOLD) {
|
|
2433
|
+
return value - UINT256_MAX - 1n;
|
|
2434
|
+
}
|
|
2435
|
+
return value;
|
|
2436
|
+
}
|
|
2518
2437
|
function sqrtPriceX96ToPrice(sqrtPriceX96) {
|
|
2519
2438
|
const priceX96 = sqrtPriceX96 * sqrtPriceX96 / Q96;
|
|
2520
2439
|
return scaleFromX96(priceX96);
|
|
@@ -2530,7 +2449,7 @@ function scaleFrom6Decimals(value) {
|
|
|
2530
2449
|
}
|
|
2531
2450
|
|
|
2532
2451
|
// src/utils/errors.ts
|
|
2533
|
-
|
|
2452
|
+
import { BaseError, ContractFunctionRevertedError } from "viem";
|
|
2534
2453
|
var ErrorCategory = /* @__PURE__ */ ((ErrorCategory2) => {
|
|
2535
2454
|
ErrorCategory2["USER_ERROR"] = "USER_ERROR";
|
|
2536
2455
|
ErrorCategory2["STATE_ERROR"] = "STATE_ERROR";
|
|
@@ -2588,9 +2507,9 @@ function parseContractError(error) {
|
|
|
2588
2507
|
if (error instanceof PerpCityError) {
|
|
2589
2508
|
return error;
|
|
2590
2509
|
}
|
|
2591
|
-
if (error instanceof
|
|
2592
|
-
const revertError = error.walk((err) => err instanceof
|
|
2593
|
-
if (revertError instanceof
|
|
2510
|
+
if (error instanceof BaseError) {
|
|
2511
|
+
const revertError = error.walk((err) => err instanceof ContractFunctionRevertedError);
|
|
2512
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
2594
2513
|
const errorName = revertError.data?.errorName ?? "Unknown";
|
|
2595
2514
|
const args = revertError.data?.args ?? [];
|
|
2596
2515
|
const { message, debug } = formatContractError(errorName, args);
|
|
@@ -2755,7 +2674,7 @@ function formatContractError(errorName, args) {
|
|
|
2755
2674
|
};
|
|
2756
2675
|
case "MinimumAmountInsufficient":
|
|
2757
2676
|
return {
|
|
2758
|
-
message:
|
|
2677
|
+
message: "Slippage tolerance exceeded. The position's value moved unfavorably during execution. Try increasing your slippage tolerance.",
|
|
2759
2678
|
debug: { source, category: "USER_ERROR" /* USER_ERROR */ }
|
|
2760
2679
|
};
|
|
2761
2680
|
case "PriceImpactTooHigh":
|
|
@@ -3034,6 +2953,23 @@ async function withErrorHandling(fn, context) {
|
|
|
3034
2953
|
}
|
|
3035
2954
|
}
|
|
3036
2955
|
|
|
2956
|
+
// src/utils/funding.ts
|
|
2957
|
+
var PRECISION = 10n ** 18n;
|
|
2958
|
+
function convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, interval, periodSeconds) {
|
|
2959
|
+
const scaledRate = fundingDiffX96 * periodSeconds * PRECISION / (Q96 * interval);
|
|
2960
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
2961
|
+
}
|
|
2962
|
+
function convertFundingPerSecondX96ToPercentPerMinute(fundingPerSecondX96) {
|
|
2963
|
+
const SECONDS_PER_MINUTE = 60n;
|
|
2964
|
+
const scaledRate = fundingPerSecondX96 * SECONDS_PER_MINUTE * PRECISION / Q96;
|
|
2965
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
2966
|
+
}
|
|
2967
|
+
function convertFundingPerSecondX96ToPercentPerDay(fundingPerSecondX96) {
|
|
2968
|
+
const SECONDS_PER_DAY = 86400n;
|
|
2969
|
+
const scaledRate = fundingPerSecondX96 * SECONDS_PER_DAY * PRECISION / Q96;
|
|
2970
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
2971
|
+
}
|
|
2972
|
+
|
|
3037
2973
|
// src/utils/liquidity.ts
|
|
3038
2974
|
async function estimateLiquidity(_context, tickLower, tickUpper, usdScaled) {
|
|
3039
2975
|
if (tickLower >= tickUpper) {
|
|
@@ -3136,16 +3072,16 @@ function getRpcUrl(config = {}) {
|
|
|
3136
3072
|
// src/context.ts
|
|
3137
3073
|
var PerpCityContext = class {
|
|
3138
3074
|
constructor(config) {
|
|
3139
|
-
this.configCache = new
|
|
3075
|
+
this.configCache = new TTLCache({ ttl: 5 * 60 * 1e3 });
|
|
3140
3076
|
this.walletClient = config.walletClient;
|
|
3141
3077
|
if (!config.walletClient.chain?.id) {
|
|
3142
3078
|
throw new Error(
|
|
3143
3079
|
"PerpCityContext: walletClient.chain must be defined with a numeric id. Ensure your walletClient was created with a chain parameter."
|
|
3144
3080
|
);
|
|
3145
3081
|
}
|
|
3146
|
-
this.publicClient =
|
|
3082
|
+
this.publicClient = createPublicClient({
|
|
3147
3083
|
chain: config.walletClient.chain,
|
|
3148
|
-
transport:
|
|
3084
|
+
transport: http(config.rpcUrl, { batch: true })
|
|
3149
3085
|
});
|
|
3150
3086
|
this._deployments = config.deployments;
|
|
3151
3087
|
}
|
|
@@ -3227,8 +3163,7 @@ var PerpCityContext = class {
|
|
|
3227
3163
|
const tickSpacing = Number(cfg.key.tickSpacing);
|
|
3228
3164
|
let sqrtPriceX96;
|
|
3229
3165
|
if (markPrice) {
|
|
3230
|
-
|
|
3231
|
-
sqrtPriceX96 = BigInt(Math.floor(sqrtPrice * 2 ** 96));
|
|
3166
|
+
sqrtPriceX96 = priceToSqrtPriceX96(markPrice);
|
|
3232
3167
|
} else {
|
|
3233
3168
|
sqrtPriceX96 = await this.publicClient.readContract({
|
|
3234
3169
|
address: this.deployments().perpManager,
|
|
@@ -3313,7 +3248,7 @@ var PerpCityContext = class {
|
|
|
3313
3248
|
async fetchUserData(userAddress, positions) {
|
|
3314
3249
|
const usdcBalance = await this.publicClient.readContract({
|
|
3315
3250
|
address: this.deployments().usdc,
|
|
3316
|
-
abi:
|
|
3251
|
+
abi: erc20Abi2,
|
|
3317
3252
|
functionName: "balanceOf",
|
|
3318
3253
|
args: [userAddress]
|
|
3319
3254
|
});
|
|
@@ -3331,7 +3266,7 @@ var PerpCityContext = class {
|
|
|
3331
3266
|
);
|
|
3332
3267
|
return {
|
|
3333
3268
|
walletAddress: userAddress,
|
|
3334
|
-
usdcBalance: Number(
|
|
3269
|
+
usdcBalance: Number(formatUnits(usdcBalance, 6)),
|
|
3335
3270
|
openPositions: openPositionsData
|
|
3336
3271
|
};
|
|
3337
3272
|
}
|
|
@@ -3350,9 +3285,10 @@ var PerpCityContext = class {
|
|
|
3350
3285
|
);
|
|
3351
3286
|
}
|
|
3352
3287
|
return {
|
|
3353
|
-
pnl: Number(
|
|
3354
|
-
|
|
3355
|
-
|
|
3288
|
+
pnl: Number(formatUnits(pnl, 6)),
|
|
3289
|
+
// Negate so positive = user receives funding
|
|
3290
|
+
fundingPayment: -Number(formatUnits(funding, 6)),
|
|
3291
|
+
effectiveMargin: Number(formatUnits(uint256ToInt256(netMargin), 6)),
|
|
3356
3292
|
isLiquidatable: wasLiquidated
|
|
3357
3293
|
};
|
|
3358
3294
|
}, `fetchPositionLiveDetailsFromContract for position ${positionId}`);
|
|
@@ -3396,29 +3332,35 @@ var PerpCityContext = class {
|
|
|
3396
3332
|
args: [positionId]
|
|
3397
3333
|
});
|
|
3398
3334
|
const resultArray = result;
|
|
3399
|
-
const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios] = resultArray;
|
|
3335
|
+
const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios, makerDetailsRaw] = resultArray;
|
|
3400
3336
|
const zeroPerpId = `0x${"0".repeat(64)}`;
|
|
3401
3337
|
if (perpId === zeroPerpId) {
|
|
3402
3338
|
throw new Error(`Position ${positionId} does not exist`);
|
|
3403
3339
|
}
|
|
3340
|
+
const isMaker = makerDetailsRaw != null && (makerDetailsRaw.tickLower !== 0 || makerDetailsRaw.tickUpper !== 0);
|
|
3404
3341
|
return {
|
|
3405
3342
|
perpId,
|
|
3406
3343
|
positionId,
|
|
3407
|
-
margin: Number(
|
|
3344
|
+
margin: Number(formatUnits(margin, 6)),
|
|
3408
3345
|
entryPerpDelta,
|
|
3409
3346
|
entryUsdDelta,
|
|
3410
3347
|
marginRatios: {
|
|
3411
3348
|
min: Number(marginRatios.min),
|
|
3412
3349
|
max: Number(marginRatios.max),
|
|
3413
3350
|
liq: Number(marginRatios.liq)
|
|
3414
|
-
}
|
|
3351
|
+
},
|
|
3352
|
+
makerDetails: isMaker ? {
|
|
3353
|
+
unlockTimestamp: Number(makerDetailsRaw.unlockTimestamp),
|
|
3354
|
+
tickLower: makerDetailsRaw.tickLower,
|
|
3355
|
+
tickUpper: makerDetailsRaw.tickUpper
|
|
3356
|
+
} : null
|
|
3415
3357
|
};
|
|
3416
3358
|
}, `getPositionRawData for position ${positionId}`);
|
|
3417
3359
|
}
|
|
3418
3360
|
};
|
|
3419
3361
|
|
|
3420
3362
|
// src/functions/open-position.ts
|
|
3421
|
-
|
|
3363
|
+
import { decodeEventLog, formatUnits as formatUnits2 } from "viem";
|
|
3422
3364
|
var OpenPosition = class _OpenPosition {
|
|
3423
3365
|
constructor(context, perpId, positionId, isLong, isMaker, txHash) {
|
|
3424
3366
|
this.context = context;
|
|
@@ -3455,7 +3397,7 @@ var OpenPosition = class _OpenPosition {
|
|
|
3455
3397
|
let _wasFullyClosed = false;
|
|
3456
3398
|
for (const log of receipt.logs) {
|
|
3457
3399
|
try {
|
|
3458
|
-
const openedDecoded =
|
|
3400
|
+
const openedDecoded = decodeEventLog({
|
|
3459
3401
|
abi: PERP_MANAGER_ABI,
|
|
3460
3402
|
data: log.data,
|
|
3461
3403
|
topics: log.topics,
|
|
@@ -3469,7 +3411,7 @@ var OpenPosition = class _OpenPosition {
|
|
|
3469
3411
|
}
|
|
3470
3412
|
} catch (_e) {
|
|
3471
3413
|
try {
|
|
3472
|
-
const closedDecoded =
|
|
3414
|
+
const closedDecoded = decodeEventLog({
|
|
3473
3415
|
abi: PERP_MANAGER_ABI,
|
|
3474
3416
|
data: log.data,
|
|
3475
3417
|
topics: log.topics,
|
|
@@ -3517,9 +3459,9 @@ var OpenPosition = class _OpenPosition {
|
|
|
3517
3459
|
);
|
|
3518
3460
|
}
|
|
3519
3461
|
return {
|
|
3520
|
-
pnl: Number((
|
|
3521
|
-
fundingPayment: Number((
|
|
3522
|
-
effectiveMargin: Number((
|
|
3462
|
+
pnl: Number(formatUnits2(pnl, 6)),
|
|
3463
|
+
fundingPayment: Number(formatUnits2(funding, 6)),
|
|
3464
|
+
effectiveMargin: Number(formatUnits2(uint256ToInt256(netMargin), 6)),
|
|
3523
3465
|
isLiquidatable: wasLiquidated
|
|
3524
3466
|
};
|
|
3525
3467
|
}, `liveDetails for position ${this.positionId}`);
|
|
@@ -3527,6 +3469,8 @@ var OpenPosition = class _OpenPosition {
|
|
|
3527
3469
|
};
|
|
3528
3470
|
|
|
3529
3471
|
// src/functions/perp.ts
|
|
3472
|
+
var TWAVG_WINDOW = 3600;
|
|
3473
|
+
var INTERVAL = 86400n;
|
|
3530
3474
|
function getPerpMark(perpData) {
|
|
3531
3475
|
return perpData.mark;
|
|
3532
3476
|
}
|
|
@@ -3542,9 +3486,58 @@ function getPerpFees(perpData) {
|
|
|
3542
3486
|
function getPerpTickSpacing(perpData) {
|
|
3543
3487
|
return perpData.tickSpacing;
|
|
3544
3488
|
}
|
|
3489
|
+
async function getFundingRate(context, perpId) {
|
|
3490
|
+
return withErrorHandling(async () => {
|
|
3491
|
+
const perpManagerAddr = context.deployments().perpManager;
|
|
3492
|
+
const cfg = await context.getPerpConfig(perpId);
|
|
3493
|
+
const [twAvgSqrtMarkX96, twAvgIndexX96] = await Promise.all([
|
|
3494
|
+
context.publicClient.readContract({
|
|
3495
|
+
address: perpManagerAddr,
|
|
3496
|
+
abi: PERP_MANAGER_ABI,
|
|
3497
|
+
functionName: "timeWeightedAvgSqrtPriceX96",
|
|
3498
|
+
args: [perpId, TWAVG_WINDOW]
|
|
3499
|
+
}),
|
|
3500
|
+
context.publicClient.readContract({
|
|
3501
|
+
address: cfg.beacon,
|
|
3502
|
+
abi: BEACON_ABI,
|
|
3503
|
+
functionName: "twAvg",
|
|
3504
|
+
args: [TWAVG_WINDOW]
|
|
3505
|
+
})
|
|
3506
|
+
]);
|
|
3507
|
+
const twAvgMarkX96 = twAvgSqrtMarkX96 * twAvgSqrtMarkX96 / Q96;
|
|
3508
|
+
const fundingDiffX96 = twAvgMarkX96 - twAvgIndexX96;
|
|
3509
|
+
const fundingPerSecondX96 = fundingDiffX96 / INTERVAL;
|
|
3510
|
+
return {
|
|
3511
|
+
ratePerDay: convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, INTERVAL, 86400n),
|
|
3512
|
+
ratePerMinute: convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, INTERVAL, 60n),
|
|
3513
|
+
rawX96: fundingPerSecondX96
|
|
3514
|
+
};
|
|
3515
|
+
}, `getFundingRate for perp ${perpId}`);
|
|
3516
|
+
}
|
|
3517
|
+
async function getIndexValue(context, perpId) {
|
|
3518
|
+
return withErrorHandling(async () => {
|
|
3519
|
+
const perpData = await context.getPerpData(perpId);
|
|
3520
|
+
return await context.publicClient.readContract({
|
|
3521
|
+
address: perpData.beacon,
|
|
3522
|
+
abi: BEACON_ABI,
|
|
3523
|
+
functionName: "index"
|
|
3524
|
+
});
|
|
3525
|
+
}, `getIndexValue for perp ${perpId}`);
|
|
3526
|
+
}
|
|
3527
|
+
async function getIndexTWAP(context, perpId, secondsAgo) {
|
|
3528
|
+
return withErrorHandling(async () => {
|
|
3529
|
+
const perpData = await context.getPerpData(perpId);
|
|
3530
|
+
return await context.publicClient.readContract({
|
|
3531
|
+
address: perpData.beacon,
|
|
3532
|
+
abi: BEACON_ABI,
|
|
3533
|
+
functionName: "twAvg",
|
|
3534
|
+
args: [secondsAgo]
|
|
3535
|
+
});
|
|
3536
|
+
}, `getIndexTWAP for perp ${perpId}`);
|
|
3537
|
+
}
|
|
3545
3538
|
|
|
3546
3539
|
// src/functions/perp-manager.ts
|
|
3547
|
-
|
|
3540
|
+
import { decodeErrorResult, decodeEventLog as decodeEventLog2, erc20Abi as erc20Abi3 } from "viem";
|
|
3548
3541
|
async function createPerp(context, params) {
|
|
3549
3542
|
return withErrorHandling(async () => {
|
|
3550
3543
|
const deployments = context.deployments();
|
|
@@ -3578,7 +3571,7 @@ async function createPerp(context, params) {
|
|
|
3578
3571
|
}
|
|
3579
3572
|
for (const log of receipt.logs) {
|
|
3580
3573
|
try {
|
|
3581
|
-
const decoded = (
|
|
3574
|
+
const decoded = decodeEventLog2({
|
|
3582
3575
|
abi: PERP_MANAGER_ABI,
|
|
3583
3576
|
data: log.data,
|
|
3584
3577
|
topics: log.topics,
|
|
@@ -3615,7 +3608,7 @@ async function openTakerPosition(context, perpId, params) {
|
|
|
3615
3608
|
const requiredAmount = marginScaled + totalFees;
|
|
3616
3609
|
const currentAllowance = await context.publicClient.readContract({
|
|
3617
3610
|
address: context.deployments().usdc,
|
|
3618
|
-
abi:
|
|
3611
|
+
abi: erc20Abi3,
|
|
3619
3612
|
functionName: "allowance",
|
|
3620
3613
|
args: [context.walletClient.account.address, context.deployments().perpManager],
|
|
3621
3614
|
blockTag: "latest"
|
|
@@ -3648,7 +3641,7 @@ async function openTakerPosition(context, perpId, params) {
|
|
|
3648
3641
|
let takerPosId = null;
|
|
3649
3642
|
for (const log of receipt.logs) {
|
|
3650
3643
|
try {
|
|
3651
|
-
const decoded = (
|
|
3644
|
+
const decoded = decodeEventLog2({
|
|
3652
3645
|
abi: PERP_MANAGER_ABI,
|
|
3653
3646
|
data: log.data,
|
|
3654
3647
|
topics: log.topics
|
|
@@ -3736,11 +3729,17 @@ async function quoteOpenMakerPosition(context, perpId, params) {
|
|
|
3736
3729
|
return { perpDelta, usdDelta };
|
|
3737
3730
|
}, "quoteOpenMakerPosition");
|
|
3738
3731
|
}
|
|
3739
|
-
function applySlippage(delta, slippageTolerance) {
|
|
3740
|
-
if (delta >= 0n) return 0n;
|
|
3741
|
-
const absDelta = -delta;
|
|
3732
|
+
function applySlippage(delta, slippageTolerance, fallbackRef) {
|
|
3742
3733
|
const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
|
|
3743
|
-
|
|
3734
|
+
if (delta < 0n) {
|
|
3735
|
+
const absDelta = -delta;
|
|
3736
|
+
return absDelta + absDelta * slippageBps / 10000n;
|
|
3737
|
+
}
|
|
3738
|
+
if (fallbackRef !== void 0 && fallbackRef !== 0n) {
|
|
3739
|
+
const absRef = fallbackRef < 0n ? -fallbackRef : fallbackRef;
|
|
3740
|
+
return absRef * slippageBps / 10000n;
|
|
3741
|
+
}
|
|
3742
|
+
return 0n;
|
|
3744
3743
|
}
|
|
3745
3744
|
async function openMakerPosition(context, perpId, params) {
|
|
3746
3745
|
return withErrorHandling(async () => {
|
|
@@ -3766,12 +3765,12 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3766
3765
|
} else {
|
|
3767
3766
|
const quote = await quoteOpenMakerPosition(context, perpId, params);
|
|
3768
3767
|
const slippage = params.slippageTolerance ?? DEFAULT_MAKER_SLIPPAGE_TOLERANCE;
|
|
3769
|
-
maxAmt0In = applySlippage(quote.perpDelta, slippage);
|
|
3770
|
-
maxAmt1In = applySlippage(quote.usdDelta, slippage);
|
|
3768
|
+
maxAmt0In = applySlippage(quote.perpDelta, slippage, quote.usdDelta);
|
|
3769
|
+
maxAmt1In = applySlippage(quote.usdDelta, slippage, quote.perpDelta);
|
|
3771
3770
|
}
|
|
3772
3771
|
const currentAllowance = await context.publicClient.readContract({
|
|
3773
3772
|
address: context.deployments().usdc,
|
|
3774
|
-
abi:
|
|
3773
|
+
abi: erc20Abi3,
|
|
3775
3774
|
functionName: "allowance",
|
|
3776
3775
|
args: [context.walletClient.account.address, context.deployments().perpManager],
|
|
3777
3776
|
blockTag: "latest"
|
|
@@ -3805,7 +3804,7 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3805
3804
|
let makerPosId = null;
|
|
3806
3805
|
for (const log of receipt.logs) {
|
|
3807
3806
|
try {
|
|
3808
|
-
const decoded = (
|
|
3807
|
+
const decoded = decodeEventLog2({
|
|
3809
3808
|
abi: PERP_MANAGER_ABI,
|
|
3810
3809
|
data: log.data,
|
|
3811
3810
|
topics: log.topics
|
|
@@ -3823,9 +3822,109 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3823
3822
|
return new OpenPosition(context, perpId, makerPosId, void 0, true, txHash);
|
|
3824
3823
|
}, "openMakerPosition");
|
|
3825
3824
|
}
|
|
3825
|
+
async function quoteTakerPosition(context, perpId, params) {
|
|
3826
|
+
return withErrorHandling(async () => {
|
|
3827
|
+
if (params.margin <= 0) {
|
|
3828
|
+
throw new Error("Margin must be greater than 0");
|
|
3829
|
+
}
|
|
3830
|
+
if (params.leverage <= 0) {
|
|
3831
|
+
throw new Error("Leverage must be greater than 0");
|
|
3832
|
+
}
|
|
3833
|
+
const marginScaled = scale6Decimals(params.margin);
|
|
3834
|
+
const marginRatio = Math.floor(NUMBER_1E6 / params.leverage);
|
|
3835
|
+
const result = await context.publicClient.simulateContract({
|
|
3836
|
+
address: context.deployments().perpManager,
|
|
3837
|
+
abi: PERP_MANAGER_ABI,
|
|
3838
|
+
functionName: "quoteOpenTakerPosition",
|
|
3839
|
+
args: [
|
|
3840
|
+
perpId,
|
|
3841
|
+
{
|
|
3842
|
+
holder: params.holder,
|
|
3843
|
+
isLong: params.isLong,
|
|
3844
|
+
margin: marginScaled,
|
|
3845
|
+
marginRatio,
|
|
3846
|
+
unspecifiedAmountLimit: params.isLong ? 0n : (1n << 128n) - 1n
|
|
3847
|
+
}
|
|
3848
|
+
],
|
|
3849
|
+
account: params.holder
|
|
3850
|
+
});
|
|
3851
|
+
const [unexpectedReason, perpDelta, usdDelta] = result.result;
|
|
3852
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
3853
|
+
let errorName = "Quote simulation failed";
|
|
3854
|
+
try {
|
|
3855
|
+
const decoded = decodeErrorResult({
|
|
3856
|
+
abi: PERP_MANAGER_ABI,
|
|
3857
|
+
data: unexpectedReason
|
|
3858
|
+
});
|
|
3859
|
+
errorName = decoded.errorName;
|
|
3860
|
+
} catch {
|
|
3861
|
+
}
|
|
3862
|
+
throw new Error(errorName);
|
|
3863
|
+
}
|
|
3864
|
+
const absPerpDelta = perpDelta < 0n ? -perpDelta : perpDelta;
|
|
3865
|
+
const absUsdDelta = usdDelta < 0n ? -usdDelta : usdDelta;
|
|
3866
|
+
if (absPerpDelta === 0n) {
|
|
3867
|
+
throw new Error("Zero position size");
|
|
3868
|
+
}
|
|
3869
|
+
const fillPrice = Number(absUsdDelta) / Number(absPerpDelta);
|
|
3870
|
+
return { perpDelta, usdDelta, fillPrice };
|
|
3871
|
+
}, "quoteTakerPosition");
|
|
3872
|
+
}
|
|
3873
|
+
async function adjustMargin(context, positionId, marginDelta) {
|
|
3874
|
+
return withErrorHandling(async () => {
|
|
3875
|
+
const deployments = context.deployments();
|
|
3876
|
+
if (marginDelta > 0n) {
|
|
3877
|
+
const currentAllowance = await context.publicClient.readContract({
|
|
3878
|
+
address: deployments.usdc,
|
|
3879
|
+
abi: erc20Abi3,
|
|
3880
|
+
functionName: "allowance",
|
|
3881
|
+
args: [context.walletClient.account.address, deployments.perpManager],
|
|
3882
|
+
blockTag: "latest"
|
|
3883
|
+
});
|
|
3884
|
+
if (currentAllowance < marginDelta) {
|
|
3885
|
+
await approveUsdc(context, marginDelta);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
const { request } = await context.publicClient.simulateContract({
|
|
3889
|
+
address: deployments.perpManager,
|
|
3890
|
+
abi: PERP_MANAGER_ABI,
|
|
3891
|
+
functionName: "adjustMargin",
|
|
3892
|
+
args: [{ posId: positionId, marginDelta }],
|
|
3893
|
+
account: context.walletClient.account
|
|
3894
|
+
});
|
|
3895
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
3896
|
+
return { txHash };
|
|
3897
|
+
}, `adjustMargin for position ${positionId}`);
|
|
3898
|
+
}
|
|
3899
|
+
async function adjustNotional(context, positionId, usdDelta, perpLimit) {
|
|
3900
|
+
return withErrorHandling(async () => {
|
|
3901
|
+
const deployments = context.deployments();
|
|
3902
|
+
if (usdDelta > 0n) {
|
|
3903
|
+
const currentAllowance = await context.publicClient.readContract({
|
|
3904
|
+
address: deployments.usdc,
|
|
3905
|
+
abi: erc20Abi3,
|
|
3906
|
+
functionName: "allowance",
|
|
3907
|
+
args: [context.walletClient.account.address, deployments.perpManager],
|
|
3908
|
+
blockTag: "latest"
|
|
3909
|
+
});
|
|
3910
|
+
if (currentAllowance < usdDelta) {
|
|
3911
|
+
await approveUsdc(context, usdDelta);
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
const { request } = await context.publicClient.simulateContract({
|
|
3915
|
+
address: deployments.perpManager,
|
|
3916
|
+
abi: PERP_MANAGER_ABI,
|
|
3917
|
+
functionName: "adjustNotional",
|
|
3918
|
+
args: [{ posId: positionId, usdDelta, perpLimit }],
|
|
3919
|
+
account: context.walletClient.account
|
|
3920
|
+
});
|
|
3921
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
3922
|
+
return { txHash };
|
|
3923
|
+
}, `adjustNotional for position ${positionId}`);
|
|
3924
|
+
}
|
|
3826
3925
|
|
|
3827
3926
|
// src/functions/position.ts
|
|
3828
|
-
|
|
3927
|
+
import { decodeErrorResult as decodeErrorResult2, decodeEventLog as decodeEventLog3, formatUnits as formatUnits3 } from "viem";
|
|
3829
3928
|
function getPositionPerpId(positionData) {
|
|
3830
3929
|
return positionData.perpId;
|
|
3831
3930
|
}
|
|
@@ -3853,6 +3952,131 @@ function getPositionEffectiveMargin(positionData) {
|
|
|
3853
3952
|
function getPositionIsLiquidatable(positionData) {
|
|
3854
3953
|
return positionData.liveDetails.isLiquidatable;
|
|
3855
3954
|
}
|
|
3955
|
+
async function quoteClosePosition(context, positionId) {
|
|
3956
|
+
return withErrorHandling(async () => {
|
|
3957
|
+
const result = await context.publicClient.readContract({
|
|
3958
|
+
address: context.deployments().perpManager,
|
|
3959
|
+
abi: PERP_MANAGER_ABI,
|
|
3960
|
+
functionName: "quoteClosePosition",
|
|
3961
|
+
args: [positionId]
|
|
3962
|
+
});
|
|
3963
|
+
const [unexpectedReason, pnl, funding, netMargin, wasLiquidated] = result;
|
|
3964
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
3965
|
+
let errorName = "Quote simulation failed";
|
|
3966
|
+
try {
|
|
3967
|
+
const decoded = decodeErrorResult2({
|
|
3968
|
+
abi: PERP_MANAGER_ABI,
|
|
3969
|
+
data: unexpectedReason
|
|
3970
|
+
});
|
|
3971
|
+
errorName = decoded.errorName;
|
|
3972
|
+
} catch {
|
|
3973
|
+
}
|
|
3974
|
+
throw new Error(errorName);
|
|
3975
|
+
}
|
|
3976
|
+
return {
|
|
3977
|
+
pnl: Number(formatUnits3(pnl, 6)),
|
|
3978
|
+
// Negate so positive = user receives funding, matching live details convention
|
|
3979
|
+
funding: -Number(formatUnits3(funding, 6)),
|
|
3980
|
+
netMargin: Number(formatUnits3(uint256ToInt256(netMargin), 6)),
|
|
3981
|
+
wasLiquidated
|
|
3982
|
+
};
|
|
3983
|
+
}, `quoteClosePosition for position ${positionId}`);
|
|
3984
|
+
}
|
|
3985
|
+
async function closePositionWithQuote(context, _perpId, positionId, slippageTolerance = 0.01) {
|
|
3986
|
+
return withErrorHandling(async () => {
|
|
3987
|
+
const rawData = await context.getPositionRawData(positionId);
|
|
3988
|
+
const isMaker = rawData.makerDetails !== null;
|
|
3989
|
+
const result = await context.publicClient.readContract({
|
|
3990
|
+
address: context.deployments().perpManager,
|
|
3991
|
+
abi: PERP_MANAGER_ABI,
|
|
3992
|
+
functionName: "quoteClosePosition",
|
|
3993
|
+
args: [positionId]
|
|
3994
|
+
});
|
|
3995
|
+
const [unexpectedReason, quotePnl] = result;
|
|
3996
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
3997
|
+
throw new Error("Quote failed \u2014 position may be invalid or already closed");
|
|
3998
|
+
}
|
|
3999
|
+
let contractParams = {
|
|
4000
|
+
posId: positionId,
|
|
4001
|
+
minAmt0Out: 0n,
|
|
4002
|
+
minAmt1Out: 0n,
|
|
4003
|
+
maxAmt1In: 0n
|
|
4004
|
+
};
|
|
4005
|
+
const canonicalPerpId = rawData.perpId;
|
|
4006
|
+
if (!isMaker) {
|
|
4007
|
+
const isLong = rawData.entryPerpDelta > 0n;
|
|
4008
|
+
const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
|
|
4009
|
+
const absEntryUsd = rawData.entryUsdDelta < 0n ? -rawData.entryUsdDelta : rawData.entryUsdDelta;
|
|
4010
|
+
if (isLong) {
|
|
4011
|
+
const swapRevenue = quotePnl + absEntryUsd;
|
|
4012
|
+
const minOut = swapRevenue > 0n ? swapRevenue - swapRevenue * slippageBps / 10000n : 0n;
|
|
4013
|
+
contractParams = {
|
|
4014
|
+
posId: positionId,
|
|
4015
|
+
minAmt0Out: 0n,
|
|
4016
|
+
minAmt1Out: minOut,
|
|
4017
|
+
maxAmt1In: 0n
|
|
4018
|
+
};
|
|
4019
|
+
} else {
|
|
4020
|
+
const swapCost = absEntryUsd - quotePnl;
|
|
4021
|
+
const maxIn = swapCost > 0n ? swapCost + swapCost * slippageBps / 10000n : 0n;
|
|
4022
|
+
contractParams = {
|
|
4023
|
+
posId: positionId,
|
|
4024
|
+
minAmt0Out: 0n,
|
|
4025
|
+
minAmt1Out: 0n,
|
|
4026
|
+
maxAmt1In: maxIn
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
const { request } = await context.publicClient.simulateContract({
|
|
4031
|
+
address: context.deployments().perpManager,
|
|
4032
|
+
abi: PERP_MANAGER_ABI,
|
|
4033
|
+
functionName: "closePosition",
|
|
4034
|
+
args: [contractParams],
|
|
4035
|
+
account: context.walletClient.account,
|
|
4036
|
+
gas: 500000n
|
|
4037
|
+
});
|
|
4038
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
4039
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4040
|
+
hash: txHash
|
|
4041
|
+
});
|
|
4042
|
+
if (receipt.status === "reverted") {
|
|
4043
|
+
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
4044
|
+
}
|
|
4045
|
+
let newPositionId = null;
|
|
4046
|
+
for (const log of receipt.logs) {
|
|
4047
|
+
try {
|
|
4048
|
+
const decoded = decodeEventLog3({
|
|
4049
|
+
abi: PERP_MANAGER_ABI,
|
|
4050
|
+
data: log.data,
|
|
4051
|
+
topics: log.topics,
|
|
4052
|
+
eventName: "PositionOpened"
|
|
4053
|
+
});
|
|
4054
|
+
const eventPerpId = decoded.args.perpId.toLowerCase();
|
|
4055
|
+
const eventPosId = decoded.args.posId;
|
|
4056
|
+
if (eventPerpId === canonicalPerpId.toLowerCase() && eventPosId !== positionId) {
|
|
4057
|
+
newPositionId = eventPosId;
|
|
4058
|
+
break;
|
|
4059
|
+
}
|
|
4060
|
+
} catch (_e) {
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
if (!newPositionId) {
|
|
4064
|
+
return { position: null, txHash };
|
|
4065
|
+
}
|
|
4066
|
+
return {
|
|
4067
|
+
position: {
|
|
4068
|
+
perpId: canonicalPerpId,
|
|
4069
|
+
positionId: newPositionId,
|
|
4070
|
+
liveDetails: await getPositionLiveDetailsFromContract(
|
|
4071
|
+
context,
|
|
4072
|
+
canonicalPerpId,
|
|
4073
|
+
newPositionId
|
|
4074
|
+
)
|
|
4075
|
+
},
|
|
4076
|
+
txHash
|
|
4077
|
+
};
|
|
4078
|
+
}, `closePositionWithQuote for position ${positionId}`);
|
|
4079
|
+
}
|
|
3856
4080
|
async function closePosition(context, perpId, positionId, params) {
|
|
3857
4081
|
return withErrorHandling(async () => {
|
|
3858
4082
|
const contractParams = {
|
|
@@ -3871,14 +4095,16 @@ async function closePosition(context, perpId, positionId, params) {
|
|
|
3871
4095
|
// Provide explicit gas limit to avoid estimation issues
|
|
3872
4096
|
});
|
|
3873
4097
|
const txHash = await context.walletClient.writeContract(request);
|
|
3874
|
-
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4098
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4099
|
+
hash: txHash
|
|
4100
|
+
});
|
|
3875
4101
|
if (receipt.status === "reverted") {
|
|
3876
4102
|
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
3877
4103
|
}
|
|
3878
4104
|
let newPositionId = null;
|
|
3879
4105
|
for (const log of receipt.logs) {
|
|
3880
4106
|
try {
|
|
3881
|
-
const decoded = (
|
|
4107
|
+
const decoded = decodeEventLog3({
|
|
3882
4108
|
abi: PERP_MANAGER_ABI,
|
|
3883
4109
|
data: log.data,
|
|
3884
4110
|
topics: log.topics,
|
|
@@ -3921,9 +4147,10 @@ async function getPositionLiveDetailsFromContract(context, _perpId, positionId)
|
|
|
3921
4147
|
);
|
|
3922
4148
|
}
|
|
3923
4149
|
return {
|
|
3924
|
-
pnl: Number((
|
|
3925
|
-
|
|
3926
|
-
|
|
4150
|
+
pnl: Number(formatUnits3(pnl, 6)),
|
|
4151
|
+
// Negate so positive = user receives funding, matching quoteClosePosition convention
|
|
4152
|
+
fundingPayment: -Number(formatUnits3(funding, 6)),
|
|
4153
|
+
effectiveMargin: Number(formatUnits3(uint256ToInt256(netMargin), 6)),
|
|
3927
4154
|
isLiquidatable: wasLiquidated
|
|
3928
4155
|
};
|
|
3929
4156
|
}, `getPositionLiveDetailsFromContract for position ${positionId}`);
|
|
@@ -3951,22 +4178,51 @@ function calculateLeverage(positionValue, effectiveMargin) {
|
|
|
3951
4178
|
}
|
|
3952
4179
|
return positionValue / effectiveMargin;
|
|
3953
4180
|
}
|
|
3954
|
-
function calculateLiquidationPrice(rawData, isLong) {
|
|
4181
|
+
function calculateLiquidationPrice(rawData, isLong, effectiveMargin) {
|
|
3955
4182
|
const entryPrice = calculateEntryPrice(rawData);
|
|
3956
4183
|
const positionSize = Math.abs(calculatePositionSize(rawData));
|
|
3957
|
-
|
|
4184
|
+
const margin = effectiveMargin ?? rawData.margin;
|
|
4185
|
+
if (positionSize === 0 || margin <= 0) {
|
|
3958
4186
|
return null;
|
|
3959
4187
|
}
|
|
3960
4188
|
const liqMarginRatio = rawData.marginRatios.liq / 1e6;
|
|
3961
4189
|
const entryNotional = positionSize * entryPrice;
|
|
3962
4190
|
if (isLong) {
|
|
3963
|
-
const liqPrice = entryPrice - (
|
|
4191
|
+
const liqPrice = entryPrice - (margin - liqMarginRatio * entryNotional) / positionSize;
|
|
3964
4192
|
return Math.max(0, liqPrice);
|
|
3965
4193
|
} else {
|
|
3966
|
-
const liqPrice = entryPrice + (
|
|
4194
|
+
const liqPrice = entryPrice + (margin - liqMarginRatio * entryNotional) / positionSize;
|
|
3967
4195
|
return liqPrice;
|
|
3968
4196
|
}
|
|
3969
4197
|
}
|
|
4198
|
+
function calculatePnlPercentage(pnl, funding, margin) {
|
|
4199
|
+
const totalPnl = pnl + funding;
|
|
4200
|
+
const initialMargin = margin - totalPnl;
|
|
4201
|
+
if (initialMargin <= 0) return 0;
|
|
4202
|
+
return totalPnl / initialMargin * 100;
|
|
4203
|
+
}
|
|
4204
|
+
function calculateClosePositionParams(opts) {
|
|
4205
|
+
if (opts.isMaker) {
|
|
4206
|
+
return { minAmt0Out: 0, minAmt1Out: 0, maxAmt1In: 0 };
|
|
4207
|
+
}
|
|
4208
|
+
if (typeof opts.isLong !== "boolean") {
|
|
4209
|
+
throw new Error("isLong must be explicitly set for taker positions");
|
|
4210
|
+
}
|
|
4211
|
+
const absNotional = Math.abs(opts.notional);
|
|
4212
|
+
if (opts.isLong) {
|
|
4213
|
+
return {
|
|
4214
|
+
minAmt0Out: 0,
|
|
4215
|
+
minAmt1Out: Math.max(0, absNotional * (1 - opts.slippagePercent / 100)),
|
|
4216
|
+
maxAmt1In: 0
|
|
4217
|
+
};
|
|
4218
|
+
} else {
|
|
4219
|
+
return {
|
|
4220
|
+
minAmt0Out: 0,
|
|
4221
|
+
minAmt1Out: 0,
|
|
4222
|
+
maxAmt1In: absNotional * (1 + opts.slippagePercent / 100)
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
3970
4226
|
|
|
3971
4227
|
// src/functions/user.ts
|
|
3972
4228
|
function getUserUsdcBalance(userData) {
|
|
@@ -3978,16 +4234,18 @@ function getUserOpenPositions(userData) {
|
|
|
3978
4234
|
function getUserWalletAddress(userData) {
|
|
3979
4235
|
return userData.walletAddress;
|
|
3980
4236
|
}
|
|
3981
|
-
|
|
3982
|
-
0 && (module.exports = {
|
|
4237
|
+
export {
|
|
3983
4238
|
BEACON_ABI,
|
|
3984
4239
|
BIGINT_1E6,
|
|
3985
4240
|
ContractError,
|
|
4241
|
+
DEFAULT_MAKER_SLIPPAGE_TOLERANCE,
|
|
3986
4242
|
ErrorCategory,
|
|
3987
4243
|
ErrorSource,
|
|
4244
|
+
INT256_THRESHOLD,
|
|
3988
4245
|
InsufficientFundsError,
|
|
3989
4246
|
MAX_PRICE,
|
|
3990
4247
|
MAX_TICK,
|
|
4248
|
+
MAX_UINT128,
|
|
3991
4249
|
MIN_PRICE,
|
|
3992
4250
|
MIN_TICK,
|
|
3993
4251
|
NUMBER_1E6,
|
|
@@ -3998,18 +4256,31 @@ function getUserWalletAddress(userData) {
|
|
|
3998
4256
|
Q96,
|
|
3999
4257
|
RPCError,
|
|
4000
4258
|
TransactionRejectedError,
|
|
4259
|
+
UINT256_MAX,
|
|
4001
4260
|
ValidationError,
|
|
4261
|
+
adjustMargin,
|
|
4262
|
+
adjustNotional,
|
|
4263
|
+
applySlippage,
|
|
4002
4264
|
approveUsdc,
|
|
4003
4265
|
calculateAlignedTicks,
|
|
4266
|
+
calculateClosePositionParams,
|
|
4004
4267
|
calculateEntryPrice,
|
|
4005
4268
|
calculateLeverage,
|
|
4006
4269
|
calculateLiquidationPrice,
|
|
4007
4270
|
calculateLiquidityForTargetRatio,
|
|
4271
|
+
calculatePnlPercentage,
|
|
4008
4272
|
calculatePositionSize,
|
|
4009
4273
|
calculatePositionValue,
|
|
4010
4274
|
closePosition,
|
|
4275
|
+
closePositionWithQuote,
|
|
4276
|
+
convertFundingDiffX96ToPercentPerPeriod,
|
|
4277
|
+
convertFundingPerSecondX96ToPercentPerDay,
|
|
4278
|
+
convertFundingPerSecondX96ToPercentPerMinute,
|
|
4011
4279
|
createPerp,
|
|
4012
4280
|
estimateLiquidity,
|
|
4281
|
+
getFundingRate,
|
|
4282
|
+
getIndexTWAP,
|
|
4283
|
+
getIndexValue,
|
|
4013
4284
|
getPerpBeacon,
|
|
4014
4285
|
getPerpBounds,
|
|
4015
4286
|
getPerpFees,
|
|
@@ -4026,6 +4297,7 @@ function getUserWalletAddress(userData) {
|
|
|
4026
4297
|
getPositionPerpId,
|
|
4027
4298
|
getPositionPnl,
|
|
4028
4299
|
getRpcUrl,
|
|
4300
|
+
getUsdcAllowance,
|
|
4029
4301
|
getUserOpenPositions,
|
|
4030
4302
|
getUserUsdcBalance,
|
|
4031
4303
|
getUserWalletAddress,
|
|
@@ -4035,13 +4307,16 @@ function getUserWalletAddress(userData) {
|
|
|
4035
4307
|
parseContractError,
|
|
4036
4308
|
priceToSqrtPriceX96,
|
|
4037
4309
|
priceToTick,
|
|
4310
|
+
quoteClosePosition,
|
|
4038
4311
|
quoteOpenMakerPosition,
|
|
4312
|
+
quoteTakerPosition,
|
|
4039
4313
|
scale6Decimals,
|
|
4040
4314
|
scaleFrom6Decimals,
|
|
4041
4315
|
scaleFromX96,
|
|
4042
4316
|
scaleToX96,
|
|
4043
4317
|
sqrtPriceX96ToPrice,
|
|
4044
4318
|
tickToPrice,
|
|
4319
|
+
uint256ToInt256,
|
|
4045
4320
|
withErrorHandling
|
|
4046
|
-
}
|
|
4321
|
+
};
|
|
4047
4322
|
//# sourceMappingURL=index.js.map
|