@strobelabs/perpcity-sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index.mjs → index.cjs} +664 -75
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +116 -9
- package/dist/index.d.ts +116 -9
- package/dist/index.js +549 -162
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/dist/index.mjs.map +0 -1
|
@@ -1,3 +1,121 @@
|
|
|
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
|
+
DEFAULT_MAKER_SLIPPAGE_TOLERANCE: () => DEFAULT_MAKER_SLIPPAGE_TOLERANCE,
|
|
37
|
+
ErrorCategory: () => ErrorCategory,
|
|
38
|
+
ErrorSource: () => ErrorSource,
|
|
39
|
+
INT256_THRESHOLD: () => INT256_THRESHOLD,
|
|
40
|
+
InsufficientFundsError: () => InsufficientFundsError,
|
|
41
|
+
MAX_PRICE: () => MAX_PRICE,
|
|
42
|
+
MAX_TICK: () => MAX_TICK,
|
|
43
|
+
MAX_UINT128: () => MAX_UINT128,
|
|
44
|
+
MIN_PRICE: () => MIN_PRICE,
|
|
45
|
+
MIN_TICK: () => MIN_TICK,
|
|
46
|
+
NUMBER_1E6: () => NUMBER_1E6,
|
|
47
|
+
OpenPosition: () => OpenPosition,
|
|
48
|
+
PERP_MANAGER_ABI: () => PERP_MANAGER_ABI,
|
|
49
|
+
PerpCityContext: () => PerpCityContext,
|
|
50
|
+
PerpCityError: () => PerpCityError,
|
|
51
|
+
Q96: () => Q96,
|
|
52
|
+
RPCError: () => RPCError,
|
|
53
|
+
TransactionRejectedError: () => TransactionRejectedError,
|
|
54
|
+
UINT256_MAX: () => UINT256_MAX,
|
|
55
|
+
ValidationError: () => ValidationError,
|
|
56
|
+
adjustMargin: () => adjustMargin,
|
|
57
|
+
adjustNotional: () => adjustNotional,
|
|
58
|
+
applySlippage: () => applySlippage,
|
|
59
|
+
approveUsdc: () => approveUsdc,
|
|
60
|
+
calculateAlignedTicks: () => calculateAlignedTicks,
|
|
61
|
+
calculateClosePositionParams: () => calculateClosePositionParams,
|
|
62
|
+
calculateEntryPrice: () => calculateEntryPrice,
|
|
63
|
+
calculateLeverage: () => calculateLeverage,
|
|
64
|
+
calculateLiquidationPrice: () => calculateLiquidationPrice,
|
|
65
|
+
calculateLiquidityForTargetRatio: () => calculateLiquidityForTargetRatio,
|
|
66
|
+
calculatePnlPercentage: () => calculatePnlPercentage,
|
|
67
|
+
calculatePositionSize: () => calculatePositionSize,
|
|
68
|
+
calculatePositionValue: () => calculatePositionValue,
|
|
69
|
+
closePosition: () => closePosition,
|
|
70
|
+
closePositionWithQuote: () => closePositionWithQuote,
|
|
71
|
+
convertFundingDiffX96ToPercentPerPeriod: () => convertFundingDiffX96ToPercentPerPeriod,
|
|
72
|
+
convertFundingPerSecondX96ToPercentPerDay: () => convertFundingPerSecondX96ToPercentPerDay,
|
|
73
|
+
convertFundingPerSecondX96ToPercentPerMinute: () => convertFundingPerSecondX96ToPercentPerMinute,
|
|
74
|
+
createPerp: () => createPerp,
|
|
75
|
+
estimateLiquidity: () => estimateLiquidity,
|
|
76
|
+
getFundingRate: () => getFundingRate,
|
|
77
|
+
getIndexTWAP: () => getIndexTWAP,
|
|
78
|
+
getIndexValue: () => getIndexValue,
|
|
79
|
+
getPerpBeacon: () => getPerpBeacon,
|
|
80
|
+
getPerpBounds: () => getPerpBounds,
|
|
81
|
+
getPerpFees: () => getPerpFees,
|
|
82
|
+
getPerpMark: () => getPerpMark,
|
|
83
|
+
getPerpTickSpacing: () => getPerpTickSpacing,
|
|
84
|
+
getPositionEffectiveMargin: () => getPositionEffectiveMargin,
|
|
85
|
+
getPositionFundingPayment: () => getPositionFundingPayment,
|
|
86
|
+
getPositionId: () => getPositionId,
|
|
87
|
+
getPositionIsLiquidatable: () => getPositionIsLiquidatable,
|
|
88
|
+
getPositionIsLong: () => getPositionIsLong,
|
|
89
|
+
getPositionIsMaker: () => getPositionIsMaker,
|
|
90
|
+
getPositionLiveDetails: () => getPositionLiveDetails,
|
|
91
|
+
getPositionLiveDetailsFromContract: () => getPositionLiveDetailsFromContract,
|
|
92
|
+
getPositionPerpId: () => getPositionPerpId,
|
|
93
|
+
getPositionPnl: () => getPositionPnl,
|
|
94
|
+
getRpcUrl: () => getRpcUrl,
|
|
95
|
+
getUsdcAllowance: () => getUsdcAllowance,
|
|
96
|
+
getUserOpenPositions: () => getUserOpenPositions,
|
|
97
|
+
getUserUsdcBalance: () => getUserUsdcBalance,
|
|
98
|
+
getUserWalletAddress: () => getUserWalletAddress,
|
|
99
|
+
marginRatioToLeverage: () => marginRatioToLeverage,
|
|
100
|
+
openMakerPosition: () => openMakerPosition,
|
|
101
|
+
openTakerPosition: () => openTakerPosition,
|
|
102
|
+
parseContractError: () => parseContractError,
|
|
103
|
+
priceToSqrtPriceX96: () => priceToSqrtPriceX96,
|
|
104
|
+
priceToTick: () => priceToTick,
|
|
105
|
+
quoteClosePosition: () => quoteClosePosition,
|
|
106
|
+
quoteOpenMakerPosition: () => quoteOpenMakerPosition,
|
|
107
|
+
quoteTakerPosition: () => quoteTakerPosition,
|
|
108
|
+
scale6Decimals: () => scale6Decimals,
|
|
109
|
+
scaleFrom6Decimals: () => scaleFrom6Decimals,
|
|
110
|
+
scaleFromX96: () => scaleFromX96,
|
|
111
|
+
scaleToX96: () => scaleToX96,
|
|
112
|
+
sqrtPriceX96ToPrice: () => sqrtPriceX96ToPrice,
|
|
113
|
+
tickToPrice: () => tickToPrice,
|
|
114
|
+
uint256ToInt256: () => uint256ToInt256,
|
|
115
|
+
withErrorHandling: () => withErrorHandling
|
|
116
|
+
});
|
|
117
|
+
module.exports = __toCommonJS(index_exports);
|
|
118
|
+
|
|
1
119
|
// src/abis/beacon.ts
|
|
2
120
|
var BEACON_ABI = [
|
|
3
121
|
{
|
|
@@ -1777,19 +1895,14 @@ var PERP_MANAGER_ABI = [
|
|
|
1777
1895
|
type: "int256"
|
|
1778
1896
|
},
|
|
1779
1897
|
{
|
|
1780
|
-
internalType: "
|
|
1898
|
+
internalType: "uint256",
|
|
1781
1899
|
name: "netMargin",
|
|
1782
|
-
type: "
|
|
1900
|
+
type: "uint256"
|
|
1783
1901
|
},
|
|
1784
1902
|
{
|
|
1785
1903
|
internalType: "bool",
|
|
1786
1904
|
name: "wasLiquidated",
|
|
1787
1905
|
type: "bool"
|
|
1788
|
-
},
|
|
1789
|
-
{
|
|
1790
|
-
internalType: "uint256",
|
|
1791
|
-
name: "notional",
|
|
1792
|
-
type: "uint256"
|
|
1793
1906
|
}
|
|
1794
1907
|
],
|
|
1795
1908
|
stateMutability: "nonpayable",
|
|
@@ -2266,13 +2379,8 @@ var PERP_MANAGER_ABI = [
|
|
|
2266
2379
|
];
|
|
2267
2380
|
|
|
2268
2381
|
// src/context.ts
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
createPublicClient,
|
|
2272
|
-
erc20Abi as erc20Abi2,
|
|
2273
|
-
formatUnits,
|
|
2274
|
-
http
|
|
2275
|
-
} from "viem";
|
|
2382
|
+
var import_ttlcache = __toESM(require("@isaacs/ttlcache"), 1);
|
|
2383
|
+
var import_viem3 = require("viem");
|
|
2276
2384
|
|
|
2277
2385
|
// src/abis/fees.ts
|
|
2278
2386
|
var FEES_ABI = [
|
|
@@ -2353,13 +2461,22 @@ var MARGIN_RATIOS_ABI = [
|
|
|
2353
2461
|
];
|
|
2354
2462
|
|
|
2355
2463
|
// src/utils/approve.ts
|
|
2356
|
-
|
|
2464
|
+
var import_viem = require("viem");
|
|
2357
2465
|
var DEFAULT_CONFIRMATIONS = 2;
|
|
2466
|
+
async function getUsdcAllowance(context, owner) {
|
|
2467
|
+
const deployments = context.deployments();
|
|
2468
|
+
return context.publicClient.readContract({
|
|
2469
|
+
address: deployments.usdc,
|
|
2470
|
+
abi: import_viem.erc20Abi,
|
|
2471
|
+
functionName: "allowance",
|
|
2472
|
+
args: [owner, deployments.perpManager]
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2358
2475
|
async function approveUsdc(context, amount, confirmations = DEFAULT_CONFIRMATIONS) {
|
|
2359
2476
|
const deployments = context.deployments();
|
|
2360
2477
|
const { request } = await context.publicClient.simulateContract({
|
|
2361
2478
|
address: deployments.usdc,
|
|
2362
|
-
abi: erc20Abi,
|
|
2479
|
+
abi: import_viem.erc20Abi,
|
|
2363
2480
|
functionName: "approve",
|
|
2364
2481
|
args: [deployments.perpManager, amount],
|
|
2365
2482
|
account: context.walletClient.account
|
|
@@ -2375,6 +2492,12 @@ async function approveUsdc(context, amount, confirmations = DEFAULT_CONFIRMATION
|
|
|
2375
2492
|
var NUMBER_1E6 = 1e6;
|
|
2376
2493
|
var BIGINT_1E6 = 1000000n;
|
|
2377
2494
|
var Q96 = 79228162514264337593543950336n;
|
|
2495
|
+
var MIN_TICK = -887272;
|
|
2496
|
+
var MAX_TICK = 887272;
|
|
2497
|
+
var MIN_PRICE = 1e-6;
|
|
2498
|
+
var MAX_PRICE = 1e6;
|
|
2499
|
+
var UINT256_MAX = (1n << 256n) - 1n;
|
|
2500
|
+
var INT256_THRESHOLD = 1n << 255n;
|
|
2378
2501
|
|
|
2379
2502
|
// src/utils/conversions.ts
|
|
2380
2503
|
function priceToSqrtPriceX96(price) {
|
|
@@ -2407,12 +2530,23 @@ function priceToTick(price, roundDown) {
|
|
|
2407
2530
|
if (price <= 0) {
|
|
2408
2531
|
throw new Error("Price must be positive");
|
|
2409
2532
|
}
|
|
2533
|
+
if (price < MIN_PRICE || price > MAX_PRICE) {
|
|
2534
|
+
throw new Error(
|
|
2535
|
+
`Price ${price} is outside the representable range [${MIN_PRICE}, ${MAX_PRICE}]`
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2410
2538
|
const logPrice = Math.log(price) / Math.log(1.0001);
|
|
2411
2539
|
return roundDown ? Math.floor(logPrice) : Math.ceil(logPrice);
|
|
2412
2540
|
}
|
|
2413
2541
|
function tickToPrice(tick) {
|
|
2414
2542
|
return 1.0001 ** tick;
|
|
2415
2543
|
}
|
|
2544
|
+
function uint256ToInt256(value) {
|
|
2545
|
+
if (value >= INT256_THRESHOLD) {
|
|
2546
|
+
return value - UINT256_MAX - 1n;
|
|
2547
|
+
}
|
|
2548
|
+
return value;
|
|
2549
|
+
}
|
|
2416
2550
|
function sqrtPriceX96ToPrice(sqrtPriceX96) {
|
|
2417
2551
|
const priceX96 = sqrtPriceX96 * sqrtPriceX96 / Q96;
|
|
2418
2552
|
return scaleFromX96(priceX96);
|
|
@@ -2428,7 +2562,7 @@ function scaleFrom6Decimals(value) {
|
|
|
2428
2562
|
}
|
|
2429
2563
|
|
|
2430
2564
|
// src/utils/errors.ts
|
|
2431
|
-
|
|
2565
|
+
var import_viem2 = require("viem");
|
|
2432
2566
|
var ErrorCategory = /* @__PURE__ */ ((ErrorCategory2) => {
|
|
2433
2567
|
ErrorCategory2["USER_ERROR"] = "USER_ERROR";
|
|
2434
2568
|
ErrorCategory2["STATE_ERROR"] = "STATE_ERROR";
|
|
@@ -2486,9 +2620,9 @@ function parseContractError(error) {
|
|
|
2486
2620
|
if (error instanceof PerpCityError) {
|
|
2487
2621
|
return error;
|
|
2488
2622
|
}
|
|
2489
|
-
if (error instanceof BaseError) {
|
|
2490
|
-
const revertError = error.walk((err) => err instanceof ContractFunctionRevertedError);
|
|
2491
|
-
if (revertError instanceof ContractFunctionRevertedError) {
|
|
2623
|
+
if (error instanceof import_viem2.BaseError) {
|
|
2624
|
+
const revertError = error.walk((err) => err instanceof import_viem2.ContractFunctionRevertedError);
|
|
2625
|
+
if (revertError instanceof import_viem2.ContractFunctionRevertedError) {
|
|
2492
2626
|
const errorName = revertError.data?.errorName ?? "Unknown";
|
|
2493
2627
|
const args = revertError.data?.args ?? [];
|
|
2494
2628
|
const { message, debug } = formatContractError(errorName, args);
|
|
@@ -2653,7 +2787,7 @@ function formatContractError(errorName, args) {
|
|
|
2653
2787
|
};
|
|
2654
2788
|
case "MinimumAmountInsufficient":
|
|
2655
2789
|
return {
|
|
2656
|
-
message:
|
|
2790
|
+
message: "Slippage tolerance exceeded. The position's value moved unfavorably during execution. Try increasing your slippage tolerance.",
|
|
2657
2791
|
debug: { source, category: "USER_ERROR" /* USER_ERROR */ }
|
|
2658
2792
|
};
|
|
2659
2793
|
case "PriceImpactTooHigh":
|
|
@@ -2932,6 +3066,23 @@ async function withErrorHandling(fn, context) {
|
|
|
2932
3066
|
}
|
|
2933
3067
|
}
|
|
2934
3068
|
|
|
3069
|
+
// src/utils/funding.ts
|
|
3070
|
+
var PRECISION = 10n ** 18n;
|
|
3071
|
+
function convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, interval, periodSeconds) {
|
|
3072
|
+
const scaledRate = fundingDiffX96 * periodSeconds * PRECISION / (Q96 * interval);
|
|
3073
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
3074
|
+
}
|
|
3075
|
+
function convertFundingPerSecondX96ToPercentPerMinute(fundingPerSecondX96) {
|
|
3076
|
+
const SECONDS_PER_MINUTE = 60n;
|
|
3077
|
+
const scaledRate = fundingPerSecondX96 * SECONDS_PER_MINUTE * PRECISION / Q96;
|
|
3078
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
3079
|
+
}
|
|
3080
|
+
function convertFundingPerSecondX96ToPercentPerDay(fundingPerSecondX96) {
|
|
3081
|
+
const SECONDS_PER_DAY = 86400n;
|
|
3082
|
+
const scaledRate = fundingPerSecondX96 * SECONDS_PER_DAY * PRECISION / Q96;
|
|
3083
|
+
return Number(scaledRate) / Number(PRECISION);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
2935
3086
|
// src/utils/liquidity.ts
|
|
2936
3087
|
async function estimateLiquidity(_context, tickLower, tickUpper, usdScaled) {
|
|
2937
3088
|
if (tickLower >= tickUpper) {
|
|
@@ -3034,16 +3185,16 @@ function getRpcUrl(config = {}) {
|
|
|
3034
3185
|
// src/context.ts
|
|
3035
3186
|
var PerpCityContext = class {
|
|
3036
3187
|
constructor(config) {
|
|
3037
|
-
this.configCache = new
|
|
3188
|
+
this.configCache = new import_ttlcache.default({ ttl: 5 * 60 * 1e3 });
|
|
3038
3189
|
this.walletClient = config.walletClient;
|
|
3039
3190
|
if (!config.walletClient.chain?.id) {
|
|
3040
3191
|
throw new Error(
|
|
3041
3192
|
"PerpCityContext: walletClient.chain must be defined with a numeric id. Ensure your walletClient was created with a chain parameter."
|
|
3042
3193
|
);
|
|
3043
3194
|
}
|
|
3044
|
-
this.publicClient = createPublicClient({
|
|
3195
|
+
this.publicClient = (0, import_viem3.createPublicClient)({
|
|
3045
3196
|
chain: config.walletClient.chain,
|
|
3046
|
-
transport: http(config.rpcUrl, { batch: true })
|
|
3197
|
+
transport: (0, import_viem3.http)(config.rpcUrl, { batch: true })
|
|
3047
3198
|
});
|
|
3048
3199
|
this._deployments = config.deployments;
|
|
3049
3200
|
}
|
|
@@ -3125,8 +3276,7 @@ var PerpCityContext = class {
|
|
|
3125
3276
|
const tickSpacing = Number(cfg.key.tickSpacing);
|
|
3126
3277
|
let sqrtPriceX96;
|
|
3127
3278
|
if (markPrice) {
|
|
3128
|
-
|
|
3129
|
-
sqrtPriceX96 = BigInt(Math.floor(sqrtPrice * 2 ** 96));
|
|
3279
|
+
sqrtPriceX96 = priceToSqrtPriceX96(markPrice);
|
|
3130
3280
|
} else {
|
|
3131
3281
|
sqrtPriceX96 = await this.publicClient.readContract({
|
|
3132
3282
|
address: this.deployments().perpManager,
|
|
@@ -3211,7 +3361,7 @@ var PerpCityContext = class {
|
|
|
3211
3361
|
async fetchUserData(userAddress, positions) {
|
|
3212
3362
|
const usdcBalance = await this.publicClient.readContract({
|
|
3213
3363
|
address: this.deployments().usdc,
|
|
3214
|
-
abi:
|
|
3364
|
+
abi: import_viem3.erc20Abi,
|
|
3215
3365
|
functionName: "balanceOf",
|
|
3216
3366
|
args: [userAddress]
|
|
3217
3367
|
});
|
|
@@ -3229,7 +3379,7 @@ var PerpCityContext = class {
|
|
|
3229
3379
|
);
|
|
3230
3380
|
return {
|
|
3231
3381
|
walletAddress: userAddress,
|
|
3232
|
-
usdcBalance: Number(formatUnits(usdcBalance, 6)),
|
|
3382
|
+
usdcBalance: Number((0, import_viem3.formatUnits)(usdcBalance, 6)),
|
|
3233
3383
|
openPositions: openPositionsData
|
|
3234
3384
|
};
|
|
3235
3385
|
}
|
|
@@ -3248,9 +3398,10 @@ var PerpCityContext = class {
|
|
|
3248
3398
|
);
|
|
3249
3399
|
}
|
|
3250
3400
|
return {
|
|
3251
|
-
pnl: Number(formatUnits(pnl, 6)),
|
|
3252
|
-
|
|
3253
|
-
|
|
3401
|
+
pnl: Number((0, import_viem3.formatUnits)(pnl, 6)),
|
|
3402
|
+
// Negate so positive = user receives funding
|
|
3403
|
+
fundingPayment: -Number((0, import_viem3.formatUnits)(funding, 6)),
|
|
3404
|
+
effectiveMargin: Number((0, import_viem3.formatUnits)(uint256ToInt256(netMargin), 6)),
|
|
3254
3405
|
isLiquidatable: wasLiquidated
|
|
3255
3406
|
};
|
|
3256
3407
|
}, `fetchPositionLiveDetailsFromContract for position ${positionId}`);
|
|
@@ -3294,29 +3445,35 @@ var PerpCityContext = class {
|
|
|
3294
3445
|
args: [positionId]
|
|
3295
3446
|
});
|
|
3296
3447
|
const resultArray = result;
|
|
3297
|
-
const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios] = resultArray;
|
|
3448
|
+
const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios, makerDetailsRaw] = resultArray;
|
|
3298
3449
|
const zeroPerpId = `0x${"0".repeat(64)}`;
|
|
3299
3450
|
if (perpId === zeroPerpId) {
|
|
3300
3451
|
throw new Error(`Position ${positionId} does not exist`);
|
|
3301
3452
|
}
|
|
3453
|
+
const isMaker = makerDetailsRaw != null && (makerDetailsRaw.tickLower !== 0 || makerDetailsRaw.tickUpper !== 0);
|
|
3302
3454
|
return {
|
|
3303
3455
|
perpId,
|
|
3304
3456
|
positionId,
|
|
3305
|
-
margin: Number(formatUnits(margin, 6)),
|
|
3457
|
+
margin: Number((0, import_viem3.formatUnits)(margin, 6)),
|
|
3306
3458
|
entryPerpDelta,
|
|
3307
3459
|
entryUsdDelta,
|
|
3308
3460
|
marginRatios: {
|
|
3309
3461
|
min: Number(marginRatios.min),
|
|
3310
3462
|
max: Number(marginRatios.max),
|
|
3311
3463
|
liq: Number(marginRatios.liq)
|
|
3312
|
-
}
|
|
3464
|
+
},
|
|
3465
|
+
makerDetails: isMaker ? {
|
|
3466
|
+
unlockTimestamp: Number(makerDetailsRaw.unlockTimestamp),
|
|
3467
|
+
tickLower: makerDetailsRaw.tickLower,
|
|
3468
|
+
tickUpper: makerDetailsRaw.tickUpper
|
|
3469
|
+
} : null
|
|
3313
3470
|
};
|
|
3314
3471
|
}, `getPositionRawData for position ${positionId}`);
|
|
3315
3472
|
}
|
|
3316
3473
|
};
|
|
3317
3474
|
|
|
3318
3475
|
// src/functions/open-position.ts
|
|
3319
|
-
|
|
3476
|
+
var import_viem4 = require("viem");
|
|
3320
3477
|
var OpenPosition = class _OpenPosition {
|
|
3321
3478
|
constructor(context, perpId, positionId, isLong, isMaker, txHash) {
|
|
3322
3479
|
this.context = context;
|
|
@@ -3353,7 +3510,7 @@ var OpenPosition = class _OpenPosition {
|
|
|
3353
3510
|
let _wasFullyClosed = false;
|
|
3354
3511
|
for (const log of receipt.logs) {
|
|
3355
3512
|
try {
|
|
3356
|
-
const openedDecoded = decodeEventLog({
|
|
3513
|
+
const openedDecoded = (0, import_viem4.decodeEventLog)({
|
|
3357
3514
|
abi: PERP_MANAGER_ABI,
|
|
3358
3515
|
data: log.data,
|
|
3359
3516
|
topics: log.topics,
|
|
@@ -3367,7 +3524,7 @@ var OpenPosition = class _OpenPosition {
|
|
|
3367
3524
|
}
|
|
3368
3525
|
} catch (_e) {
|
|
3369
3526
|
try {
|
|
3370
|
-
const closedDecoded = decodeEventLog({
|
|
3527
|
+
const closedDecoded = (0, import_viem4.decodeEventLog)({
|
|
3371
3528
|
abi: PERP_MANAGER_ABI,
|
|
3372
3529
|
data: log.data,
|
|
3373
3530
|
topics: log.topics,
|
|
@@ -3415,9 +3572,9 @@ var OpenPosition = class _OpenPosition {
|
|
|
3415
3572
|
);
|
|
3416
3573
|
}
|
|
3417
3574
|
return {
|
|
3418
|
-
pnl: Number(
|
|
3419
|
-
fundingPayment: Number(
|
|
3420
|
-
effectiveMargin: Number(
|
|
3575
|
+
pnl: Number((0, import_viem4.formatUnits)(pnl, 6)),
|
|
3576
|
+
fundingPayment: Number((0, import_viem4.formatUnits)(funding, 6)),
|
|
3577
|
+
effectiveMargin: Number((0, import_viem4.formatUnits)(uint256ToInt256(netMargin), 6)),
|
|
3421
3578
|
isLiquidatable: wasLiquidated
|
|
3422
3579
|
};
|
|
3423
3580
|
}, `liveDetails for position ${this.positionId}`);
|
|
@@ -3425,6 +3582,8 @@ var OpenPosition = class _OpenPosition {
|
|
|
3425
3582
|
};
|
|
3426
3583
|
|
|
3427
3584
|
// src/functions/perp.ts
|
|
3585
|
+
var TWAVG_WINDOW = 3600;
|
|
3586
|
+
var INTERVAL = 86400n;
|
|
3428
3587
|
function getPerpMark(perpData) {
|
|
3429
3588
|
return perpData.mark;
|
|
3430
3589
|
}
|
|
@@ -3440,9 +3599,58 @@ function getPerpFees(perpData) {
|
|
|
3440
3599
|
function getPerpTickSpacing(perpData) {
|
|
3441
3600
|
return perpData.tickSpacing;
|
|
3442
3601
|
}
|
|
3602
|
+
async function getFundingRate(context, perpId) {
|
|
3603
|
+
return withErrorHandling(async () => {
|
|
3604
|
+
const perpManagerAddr = context.deployments().perpManager;
|
|
3605
|
+
const cfg = await context.getPerpConfig(perpId);
|
|
3606
|
+
const [twAvgSqrtMarkX96, twAvgIndexX96] = await Promise.all([
|
|
3607
|
+
context.publicClient.readContract({
|
|
3608
|
+
address: perpManagerAddr,
|
|
3609
|
+
abi: PERP_MANAGER_ABI,
|
|
3610
|
+
functionName: "timeWeightedAvgSqrtPriceX96",
|
|
3611
|
+
args: [perpId, TWAVG_WINDOW]
|
|
3612
|
+
}),
|
|
3613
|
+
context.publicClient.readContract({
|
|
3614
|
+
address: cfg.beacon,
|
|
3615
|
+
abi: BEACON_ABI,
|
|
3616
|
+
functionName: "twAvg",
|
|
3617
|
+
args: [TWAVG_WINDOW]
|
|
3618
|
+
})
|
|
3619
|
+
]);
|
|
3620
|
+
const twAvgMarkX96 = twAvgSqrtMarkX96 * twAvgSqrtMarkX96 / Q96;
|
|
3621
|
+
const fundingDiffX96 = twAvgMarkX96 - twAvgIndexX96;
|
|
3622
|
+
const fundingPerSecondX96 = fundingDiffX96 / INTERVAL;
|
|
3623
|
+
return {
|
|
3624
|
+
ratePerDay: convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, INTERVAL, 86400n),
|
|
3625
|
+
ratePerMinute: convertFundingDiffX96ToPercentPerPeriod(fundingDiffX96, INTERVAL, 60n),
|
|
3626
|
+
rawX96: fundingPerSecondX96
|
|
3627
|
+
};
|
|
3628
|
+
}, `getFundingRate for perp ${perpId}`);
|
|
3629
|
+
}
|
|
3630
|
+
async function getIndexValue(context, perpId) {
|
|
3631
|
+
return withErrorHandling(async () => {
|
|
3632
|
+
const perpData = await context.getPerpData(perpId);
|
|
3633
|
+
return await context.publicClient.readContract({
|
|
3634
|
+
address: perpData.beacon,
|
|
3635
|
+
abi: BEACON_ABI,
|
|
3636
|
+
functionName: "index"
|
|
3637
|
+
});
|
|
3638
|
+
}, `getIndexValue for perp ${perpId}`);
|
|
3639
|
+
}
|
|
3640
|
+
async function getIndexTWAP(context, perpId, secondsAgo) {
|
|
3641
|
+
return withErrorHandling(async () => {
|
|
3642
|
+
const perpData = await context.getPerpData(perpId);
|
|
3643
|
+
return await context.publicClient.readContract({
|
|
3644
|
+
address: perpData.beacon,
|
|
3645
|
+
abi: BEACON_ABI,
|
|
3646
|
+
functionName: "twAvg",
|
|
3647
|
+
args: [secondsAgo]
|
|
3648
|
+
});
|
|
3649
|
+
}, `getIndexTWAP for perp ${perpId}`);
|
|
3650
|
+
}
|
|
3443
3651
|
|
|
3444
3652
|
// src/functions/perp-manager.ts
|
|
3445
|
-
|
|
3653
|
+
var import_viem5 = require("viem");
|
|
3446
3654
|
async function createPerp(context, params) {
|
|
3447
3655
|
return withErrorHandling(async () => {
|
|
3448
3656
|
const deployments = context.deployments();
|
|
@@ -3468,13 +3676,15 @@ async function createPerp(context, params) {
|
|
|
3468
3676
|
account: context.walletClient.account
|
|
3469
3677
|
});
|
|
3470
3678
|
const txHash = await context.walletClient.writeContract(request);
|
|
3471
|
-
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3679
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3680
|
+
hash: txHash
|
|
3681
|
+
});
|
|
3472
3682
|
if (receipt.status === "reverted") {
|
|
3473
3683
|
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
3474
3684
|
}
|
|
3475
3685
|
for (const log of receipt.logs) {
|
|
3476
3686
|
try {
|
|
3477
|
-
const decoded =
|
|
3687
|
+
const decoded = (0, import_viem5.decodeEventLog)({
|
|
3478
3688
|
abi: PERP_MANAGER_ABI,
|
|
3479
3689
|
data: log.data,
|
|
3480
3690
|
topics: log.topics,
|
|
@@ -3511,7 +3721,7 @@ async function openTakerPosition(context, perpId, params) {
|
|
|
3511
3721
|
const requiredAmount = marginScaled + totalFees;
|
|
3512
3722
|
const currentAllowance = await context.publicClient.readContract({
|
|
3513
3723
|
address: context.deployments().usdc,
|
|
3514
|
-
abi:
|
|
3724
|
+
abi: import_viem5.erc20Abi,
|
|
3515
3725
|
functionName: "allowance",
|
|
3516
3726
|
args: [context.walletClient.account.address, context.deployments().perpManager],
|
|
3517
3727
|
blockTag: "latest"
|
|
@@ -3535,14 +3745,16 @@ async function openTakerPosition(context, perpId, params) {
|
|
|
3535
3745
|
account: context.walletClient.account
|
|
3536
3746
|
});
|
|
3537
3747
|
const txHash = await context.walletClient.writeContract(request);
|
|
3538
|
-
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3748
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3749
|
+
hash: txHash
|
|
3750
|
+
});
|
|
3539
3751
|
if (receipt.status === "reverted") {
|
|
3540
3752
|
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
3541
3753
|
}
|
|
3542
3754
|
let takerPosId = null;
|
|
3543
3755
|
for (const log of receipt.logs) {
|
|
3544
3756
|
try {
|
|
3545
|
-
const decoded =
|
|
3757
|
+
const decoded = (0, import_viem5.decodeEventLog)({
|
|
3546
3758
|
abi: PERP_MANAGER_ABI,
|
|
3547
3759
|
data: log.data,
|
|
3548
3760
|
topics: log.topics
|
|
@@ -3560,6 +3772,88 @@ async function openTakerPosition(context, perpId, params) {
|
|
|
3560
3772
|
return new OpenPosition(context, perpId, takerPosId, params.isLong, false, txHash);
|
|
3561
3773
|
}, "openTakerPosition");
|
|
3562
3774
|
}
|
|
3775
|
+
function buildMakerContractParams(context, marginScaled, params, alignedTickLower, alignedTickUpper, maxAmt0In, maxAmt1In) {
|
|
3776
|
+
return {
|
|
3777
|
+
holder: context.walletClient.account.address,
|
|
3778
|
+
margin: marginScaled,
|
|
3779
|
+
liquidity: params.liquidity,
|
|
3780
|
+
tickLower: alignedTickLower,
|
|
3781
|
+
tickUpper: alignedTickUpper,
|
|
3782
|
+
maxAmt0In,
|
|
3783
|
+
maxAmt1In
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
function calculateAlignedTicks(priceLower, priceUpper, tickSpacing) {
|
|
3787
|
+
const tickLower = priceToTick(priceLower, true);
|
|
3788
|
+
const tickUpper = priceToTick(priceUpper, false);
|
|
3789
|
+
const alignedTickLower = Math.floor(tickLower / tickSpacing) * tickSpacing;
|
|
3790
|
+
const alignedTickUpper = Math.ceil(tickUpper / tickSpacing) * tickSpacing;
|
|
3791
|
+
if (alignedTickLower < MIN_TICK) {
|
|
3792
|
+
throw new Error(
|
|
3793
|
+
`Lower tick ${alignedTickLower} is below MIN_TICK (${MIN_TICK}). Increase priceLower.`
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
if (alignedTickUpper > MAX_TICK) {
|
|
3797
|
+
throw new Error(
|
|
3798
|
+
`Upper tick ${alignedTickUpper} exceeds MAX_TICK (${MAX_TICK}). Decrease priceUpper.`
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
if (alignedTickLower === alignedTickUpper) {
|
|
3802
|
+
throw new Error(
|
|
3803
|
+
"Price range too narrow: lower and upper ticks are equal after alignment. Widen the range."
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
return { alignedTickLower, alignedTickUpper };
|
|
3807
|
+
}
|
|
3808
|
+
function alignMakerTicks(params, tickSpacing) {
|
|
3809
|
+
return calculateAlignedTicks(params.priceLower, params.priceUpper, tickSpacing);
|
|
3810
|
+
}
|
|
3811
|
+
var DEFAULT_MAKER_SLIPPAGE_TOLERANCE = 0.01;
|
|
3812
|
+
var MAX_UINT128 = 2n ** 128n - 1n;
|
|
3813
|
+
async function quoteOpenMakerPosition(context, perpId, params) {
|
|
3814
|
+
return withErrorHandling(async () => {
|
|
3815
|
+
if (params.margin <= 0) {
|
|
3816
|
+
throw new Error("Margin must be greater than 0");
|
|
3817
|
+
}
|
|
3818
|
+
if (params.priceLower >= params.priceUpper) {
|
|
3819
|
+
throw new Error("priceLower must be less than priceUpper");
|
|
3820
|
+
}
|
|
3821
|
+
const marginScaled = scale6Decimals(params.margin);
|
|
3822
|
+
const perpData = await context.getPerpData(perpId);
|
|
3823
|
+
const { alignedTickLower, alignedTickUpper } = alignMakerTicks(params, perpData.tickSpacing);
|
|
3824
|
+
const contractParams = buildMakerContractParams(
|
|
3825
|
+
context,
|
|
3826
|
+
marginScaled,
|
|
3827
|
+
params,
|
|
3828
|
+
alignedTickLower,
|
|
3829
|
+
alignedTickUpper,
|
|
3830
|
+
MAX_UINT128,
|
|
3831
|
+
MAX_UINT128
|
|
3832
|
+
);
|
|
3833
|
+
const [unexpectedReason, perpDelta, usdDelta] = await context.publicClient.readContract({
|
|
3834
|
+
address: context.deployments().perpManager,
|
|
3835
|
+
abi: PERP_MANAGER_ABI,
|
|
3836
|
+
functionName: "quoteOpenMakerPosition",
|
|
3837
|
+
args: [perpId, contractParams]
|
|
3838
|
+
});
|
|
3839
|
+
if (unexpectedReason !== "0x") {
|
|
3840
|
+
throw new Error(`Quote failed: ${unexpectedReason}`);
|
|
3841
|
+
}
|
|
3842
|
+
return { perpDelta, usdDelta };
|
|
3843
|
+
}, "quoteOpenMakerPosition");
|
|
3844
|
+
}
|
|
3845
|
+
function applySlippage(delta, slippageTolerance, fallbackRef) {
|
|
3846
|
+
const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
|
|
3847
|
+
if (delta < 0n) {
|
|
3848
|
+
const absDelta = -delta;
|
|
3849
|
+
return absDelta + absDelta * slippageBps / 10000n;
|
|
3850
|
+
}
|
|
3851
|
+
if (fallbackRef !== void 0 && fallbackRef !== 0n) {
|
|
3852
|
+
const absRef = fallbackRef < 0n ? -fallbackRef : fallbackRef;
|
|
3853
|
+
return absRef * slippageBps / 10000n;
|
|
3854
|
+
}
|
|
3855
|
+
return 0n;
|
|
3856
|
+
}
|
|
3563
3857
|
async function openMakerPosition(context, perpId, params) {
|
|
3564
3858
|
return withErrorHandling(async () => {
|
|
3565
3859
|
if (params.margin <= 0) {
|
|
@@ -3569,11 +3863,27 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3569
3863
|
throw new Error("priceLower must be less than priceUpper");
|
|
3570
3864
|
}
|
|
3571
3865
|
const marginScaled = scale6Decimals(params.margin);
|
|
3572
|
-
const
|
|
3573
|
-
const
|
|
3866
|
+
const perpData = await context.getPerpData(perpId);
|
|
3867
|
+
const { alignedTickLower, alignedTickUpper } = alignMakerTicks(params, perpData.tickSpacing);
|
|
3868
|
+
let maxAmt0In;
|
|
3869
|
+
let maxAmt1In;
|
|
3870
|
+
if (params.maxAmt0In === void 0 !== (params.maxAmt1In === void 0)) {
|
|
3871
|
+
throw new Error(
|
|
3872
|
+
"Both maxAmt0In and maxAmt1In must be provided together or neither. Omit both to use automatic quote-based slippage calculation."
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
if (params.maxAmt0In !== void 0 && params.maxAmt1In !== void 0) {
|
|
3876
|
+
maxAmt0In = typeof params.maxAmt0In === "bigint" ? params.maxAmt0In : scale6Decimals(params.maxAmt0In);
|
|
3877
|
+
maxAmt1In = typeof params.maxAmt1In === "bigint" ? params.maxAmt1In : scale6Decimals(params.maxAmt1In);
|
|
3878
|
+
} else {
|
|
3879
|
+
const quote = await quoteOpenMakerPosition(context, perpId, params);
|
|
3880
|
+
const slippage = params.slippageTolerance ?? DEFAULT_MAKER_SLIPPAGE_TOLERANCE;
|
|
3881
|
+
maxAmt0In = applySlippage(quote.perpDelta, slippage, quote.usdDelta);
|
|
3882
|
+
maxAmt1In = applySlippage(quote.usdDelta, slippage, quote.perpDelta);
|
|
3883
|
+
}
|
|
3574
3884
|
const currentAllowance = await context.publicClient.readContract({
|
|
3575
3885
|
address: context.deployments().usdc,
|
|
3576
|
-
abi:
|
|
3886
|
+
abi: import_viem5.erc20Abi,
|
|
3577
3887
|
functionName: "allowance",
|
|
3578
3888
|
args: [context.walletClient.account.address, context.deployments().perpManager],
|
|
3579
3889
|
blockTag: "latest"
|
|
@@ -3581,21 +3891,15 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3581
3891
|
if (currentAllowance < marginScaled) {
|
|
3582
3892
|
await approveUsdc(context, marginScaled);
|
|
3583
3893
|
}
|
|
3584
|
-
const
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
liquidity: params.liquidity,
|
|
3594
|
-
tickLower: alignedTickLower,
|
|
3595
|
-
tickUpper: alignedTickUpper,
|
|
3596
|
-
maxAmt0In: maxAmt0InScaled,
|
|
3597
|
-
maxAmt1In: maxAmt1InScaled
|
|
3598
|
-
};
|
|
3894
|
+
const contractParams = buildMakerContractParams(
|
|
3895
|
+
context,
|
|
3896
|
+
marginScaled,
|
|
3897
|
+
params,
|
|
3898
|
+
alignedTickLower,
|
|
3899
|
+
alignedTickUpper,
|
|
3900
|
+
maxAmt0In,
|
|
3901
|
+
maxAmt1In
|
|
3902
|
+
);
|
|
3599
3903
|
const { request } = await context.publicClient.simulateContract({
|
|
3600
3904
|
address: context.deployments().perpManager,
|
|
3601
3905
|
abi: PERP_MANAGER_ABI,
|
|
@@ -3604,14 +3908,16 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3604
3908
|
account: context.walletClient.account
|
|
3605
3909
|
});
|
|
3606
3910
|
const txHash = await context.walletClient.writeContract(request);
|
|
3607
|
-
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3911
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
3912
|
+
hash: txHash
|
|
3913
|
+
});
|
|
3608
3914
|
if (receipt.status === "reverted") {
|
|
3609
3915
|
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
3610
3916
|
}
|
|
3611
3917
|
let makerPosId = null;
|
|
3612
3918
|
for (const log of receipt.logs) {
|
|
3613
3919
|
try {
|
|
3614
|
-
const decoded =
|
|
3920
|
+
const decoded = (0, import_viem5.decodeEventLog)({
|
|
3615
3921
|
abi: PERP_MANAGER_ABI,
|
|
3616
3922
|
data: log.data,
|
|
3617
3923
|
topics: log.topics
|
|
@@ -3629,9 +3935,109 @@ async function openMakerPosition(context, perpId, params) {
|
|
|
3629
3935
|
return new OpenPosition(context, perpId, makerPosId, void 0, true, txHash);
|
|
3630
3936
|
}, "openMakerPosition");
|
|
3631
3937
|
}
|
|
3938
|
+
async function quoteTakerPosition(context, perpId, params) {
|
|
3939
|
+
return withErrorHandling(async () => {
|
|
3940
|
+
if (params.margin <= 0) {
|
|
3941
|
+
throw new Error("Margin must be greater than 0");
|
|
3942
|
+
}
|
|
3943
|
+
if (params.leverage <= 0) {
|
|
3944
|
+
throw new Error("Leverage must be greater than 0");
|
|
3945
|
+
}
|
|
3946
|
+
const marginScaled = scale6Decimals(params.margin);
|
|
3947
|
+
const marginRatio = Math.floor(NUMBER_1E6 / params.leverage);
|
|
3948
|
+
const result = await context.publicClient.simulateContract({
|
|
3949
|
+
address: context.deployments().perpManager,
|
|
3950
|
+
abi: PERP_MANAGER_ABI,
|
|
3951
|
+
functionName: "quoteOpenTakerPosition",
|
|
3952
|
+
args: [
|
|
3953
|
+
perpId,
|
|
3954
|
+
{
|
|
3955
|
+
holder: params.holder,
|
|
3956
|
+
isLong: params.isLong,
|
|
3957
|
+
margin: marginScaled,
|
|
3958
|
+
marginRatio,
|
|
3959
|
+
unspecifiedAmountLimit: params.isLong ? 0n : (1n << 128n) - 1n
|
|
3960
|
+
}
|
|
3961
|
+
],
|
|
3962
|
+
account: params.holder
|
|
3963
|
+
});
|
|
3964
|
+
const [unexpectedReason, perpDelta, usdDelta] = result.result;
|
|
3965
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
3966
|
+
let errorName = "Quote simulation failed";
|
|
3967
|
+
try {
|
|
3968
|
+
const decoded = (0, import_viem5.decodeErrorResult)({
|
|
3969
|
+
abi: PERP_MANAGER_ABI,
|
|
3970
|
+
data: unexpectedReason
|
|
3971
|
+
});
|
|
3972
|
+
errorName = decoded.errorName;
|
|
3973
|
+
} catch {
|
|
3974
|
+
}
|
|
3975
|
+
throw new Error(errorName);
|
|
3976
|
+
}
|
|
3977
|
+
const absPerpDelta = perpDelta < 0n ? -perpDelta : perpDelta;
|
|
3978
|
+
const absUsdDelta = usdDelta < 0n ? -usdDelta : usdDelta;
|
|
3979
|
+
if (absPerpDelta === 0n) {
|
|
3980
|
+
throw new Error("Zero position size");
|
|
3981
|
+
}
|
|
3982
|
+
const fillPrice = Number(absUsdDelta) / Number(absPerpDelta);
|
|
3983
|
+
return { perpDelta, usdDelta, fillPrice };
|
|
3984
|
+
}, "quoteTakerPosition");
|
|
3985
|
+
}
|
|
3986
|
+
async function adjustMargin(context, positionId, marginDelta) {
|
|
3987
|
+
return withErrorHandling(async () => {
|
|
3988
|
+
const deployments = context.deployments();
|
|
3989
|
+
if (marginDelta > 0n) {
|
|
3990
|
+
const currentAllowance = await context.publicClient.readContract({
|
|
3991
|
+
address: deployments.usdc,
|
|
3992
|
+
abi: import_viem5.erc20Abi,
|
|
3993
|
+
functionName: "allowance",
|
|
3994
|
+
args: [context.walletClient.account.address, deployments.perpManager],
|
|
3995
|
+
blockTag: "latest"
|
|
3996
|
+
});
|
|
3997
|
+
if (currentAllowance < marginDelta) {
|
|
3998
|
+
await approveUsdc(context, marginDelta);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
const { request } = await context.publicClient.simulateContract({
|
|
4002
|
+
address: deployments.perpManager,
|
|
4003
|
+
abi: PERP_MANAGER_ABI,
|
|
4004
|
+
functionName: "adjustMargin",
|
|
4005
|
+
args: [{ posId: positionId, marginDelta }],
|
|
4006
|
+
account: context.walletClient.account
|
|
4007
|
+
});
|
|
4008
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
4009
|
+
return { txHash };
|
|
4010
|
+
}, `adjustMargin for position ${positionId}`);
|
|
4011
|
+
}
|
|
4012
|
+
async function adjustNotional(context, positionId, usdDelta, perpLimit) {
|
|
4013
|
+
return withErrorHandling(async () => {
|
|
4014
|
+
const deployments = context.deployments();
|
|
4015
|
+
if (usdDelta > 0n) {
|
|
4016
|
+
const currentAllowance = await context.publicClient.readContract({
|
|
4017
|
+
address: deployments.usdc,
|
|
4018
|
+
abi: import_viem5.erc20Abi,
|
|
4019
|
+
functionName: "allowance",
|
|
4020
|
+
args: [context.walletClient.account.address, deployments.perpManager],
|
|
4021
|
+
blockTag: "latest"
|
|
4022
|
+
});
|
|
4023
|
+
if (currentAllowance < usdDelta) {
|
|
4024
|
+
await approveUsdc(context, usdDelta);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
const { request } = await context.publicClient.simulateContract({
|
|
4028
|
+
address: deployments.perpManager,
|
|
4029
|
+
abi: PERP_MANAGER_ABI,
|
|
4030
|
+
functionName: "adjustNotional",
|
|
4031
|
+
args: [{ posId: positionId, usdDelta, perpLimit }],
|
|
4032
|
+
account: context.walletClient.account
|
|
4033
|
+
});
|
|
4034
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
4035
|
+
return { txHash };
|
|
4036
|
+
}, `adjustNotional for position ${positionId}`);
|
|
4037
|
+
}
|
|
3632
4038
|
|
|
3633
4039
|
// src/functions/position.ts
|
|
3634
|
-
|
|
4040
|
+
var import_viem6 = require("viem");
|
|
3635
4041
|
function getPositionPerpId(positionData) {
|
|
3636
4042
|
return positionData.perpId;
|
|
3637
4043
|
}
|
|
@@ -3659,6 +4065,131 @@ function getPositionEffectiveMargin(positionData) {
|
|
|
3659
4065
|
function getPositionIsLiquidatable(positionData) {
|
|
3660
4066
|
return positionData.liveDetails.isLiquidatable;
|
|
3661
4067
|
}
|
|
4068
|
+
async function quoteClosePosition(context, positionId) {
|
|
4069
|
+
return withErrorHandling(async () => {
|
|
4070
|
+
const result = await context.publicClient.readContract({
|
|
4071
|
+
address: context.deployments().perpManager,
|
|
4072
|
+
abi: PERP_MANAGER_ABI,
|
|
4073
|
+
functionName: "quoteClosePosition",
|
|
4074
|
+
args: [positionId]
|
|
4075
|
+
});
|
|
4076
|
+
const [unexpectedReason, pnl, funding, netMargin, wasLiquidated] = result;
|
|
4077
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
4078
|
+
let errorName = "Quote simulation failed";
|
|
4079
|
+
try {
|
|
4080
|
+
const decoded = (0, import_viem6.decodeErrorResult)({
|
|
4081
|
+
abi: PERP_MANAGER_ABI,
|
|
4082
|
+
data: unexpectedReason
|
|
4083
|
+
});
|
|
4084
|
+
errorName = decoded.errorName;
|
|
4085
|
+
} catch {
|
|
4086
|
+
}
|
|
4087
|
+
throw new Error(errorName);
|
|
4088
|
+
}
|
|
4089
|
+
return {
|
|
4090
|
+
pnl: Number((0, import_viem6.formatUnits)(pnl, 6)),
|
|
4091
|
+
// Negate so positive = user receives funding, matching live details convention
|
|
4092
|
+
funding: -Number((0, import_viem6.formatUnits)(funding, 6)),
|
|
4093
|
+
netMargin: Number((0, import_viem6.formatUnits)(uint256ToInt256(netMargin), 6)),
|
|
4094
|
+
wasLiquidated
|
|
4095
|
+
};
|
|
4096
|
+
}, `quoteClosePosition for position ${positionId}`);
|
|
4097
|
+
}
|
|
4098
|
+
async function closePositionWithQuote(context, _perpId, positionId, slippageTolerance = 0.01) {
|
|
4099
|
+
return withErrorHandling(async () => {
|
|
4100
|
+
const rawData = await context.getPositionRawData(positionId);
|
|
4101
|
+
const isMaker = rawData.makerDetails !== null;
|
|
4102
|
+
const result = await context.publicClient.readContract({
|
|
4103
|
+
address: context.deployments().perpManager,
|
|
4104
|
+
abi: PERP_MANAGER_ABI,
|
|
4105
|
+
functionName: "quoteClosePosition",
|
|
4106
|
+
args: [positionId]
|
|
4107
|
+
});
|
|
4108
|
+
const [unexpectedReason, quotePnl] = result;
|
|
4109
|
+
if (unexpectedReason && unexpectedReason !== "0x") {
|
|
4110
|
+
throw new Error("Quote failed \u2014 position may be invalid or already closed");
|
|
4111
|
+
}
|
|
4112
|
+
let contractParams = {
|
|
4113
|
+
posId: positionId,
|
|
4114
|
+
minAmt0Out: 0n,
|
|
4115
|
+
minAmt1Out: 0n,
|
|
4116
|
+
maxAmt1In: 0n
|
|
4117
|
+
};
|
|
4118
|
+
const canonicalPerpId = rawData.perpId;
|
|
4119
|
+
if (!isMaker) {
|
|
4120
|
+
const isLong = rawData.entryPerpDelta > 0n;
|
|
4121
|
+
const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
|
|
4122
|
+
const absEntryUsd = rawData.entryUsdDelta < 0n ? -rawData.entryUsdDelta : rawData.entryUsdDelta;
|
|
4123
|
+
if (isLong) {
|
|
4124
|
+
const swapRevenue = quotePnl + absEntryUsd;
|
|
4125
|
+
const minOut = swapRevenue > 0n ? swapRevenue - swapRevenue * slippageBps / 10000n : 0n;
|
|
4126
|
+
contractParams = {
|
|
4127
|
+
posId: positionId,
|
|
4128
|
+
minAmt0Out: 0n,
|
|
4129
|
+
minAmt1Out: minOut,
|
|
4130
|
+
maxAmt1In: 0n
|
|
4131
|
+
};
|
|
4132
|
+
} else {
|
|
4133
|
+
const swapCost = absEntryUsd - quotePnl;
|
|
4134
|
+
const maxIn = swapCost > 0n ? swapCost + swapCost * slippageBps / 10000n : 0n;
|
|
4135
|
+
contractParams = {
|
|
4136
|
+
posId: positionId,
|
|
4137
|
+
minAmt0Out: 0n,
|
|
4138
|
+
minAmt1Out: 0n,
|
|
4139
|
+
maxAmt1In: maxIn
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
const { request } = await context.publicClient.simulateContract({
|
|
4144
|
+
address: context.deployments().perpManager,
|
|
4145
|
+
abi: PERP_MANAGER_ABI,
|
|
4146
|
+
functionName: "closePosition",
|
|
4147
|
+
args: [contractParams],
|
|
4148
|
+
account: context.walletClient.account,
|
|
4149
|
+
gas: 500000n
|
|
4150
|
+
});
|
|
4151
|
+
const txHash = await context.walletClient.writeContract(request);
|
|
4152
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4153
|
+
hash: txHash
|
|
4154
|
+
});
|
|
4155
|
+
if (receipt.status === "reverted") {
|
|
4156
|
+
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
4157
|
+
}
|
|
4158
|
+
let newPositionId = null;
|
|
4159
|
+
for (const log of receipt.logs) {
|
|
4160
|
+
try {
|
|
4161
|
+
const decoded = (0, import_viem6.decodeEventLog)({
|
|
4162
|
+
abi: PERP_MANAGER_ABI,
|
|
4163
|
+
data: log.data,
|
|
4164
|
+
topics: log.topics,
|
|
4165
|
+
eventName: "PositionOpened"
|
|
4166
|
+
});
|
|
4167
|
+
const eventPerpId = decoded.args.perpId.toLowerCase();
|
|
4168
|
+
const eventPosId = decoded.args.posId;
|
|
4169
|
+
if (eventPerpId === canonicalPerpId.toLowerCase() && eventPosId !== positionId) {
|
|
4170
|
+
newPositionId = eventPosId;
|
|
4171
|
+
break;
|
|
4172
|
+
}
|
|
4173
|
+
} catch (_e) {
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
if (!newPositionId) {
|
|
4177
|
+
return { position: null, txHash };
|
|
4178
|
+
}
|
|
4179
|
+
return {
|
|
4180
|
+
position: {
|
|
4181
|
+
perpId: canonicalPerpId,
|
|
4182
|
+
positionId: newPositionId,
|
|
4183
|
+
liveDetails: await getPositionLiveDetailsFromContract(
|
|
4184
|
+
context,
|
|
4185
|
+
canonicalPerpId,
|
|
4186
|
+
newPositionId
|
|
4187
|
+
)
|
|
4188
|
+
},
|
|
4189
|
+
txHash
|
|
4190
|
+
};
|
|
4191
|
+
}, `closePositionWithQuote for position ${positionId}`);
|
|
4192
|
+
}
|
|
3662
4193
|
async function closePosition(context, perpId, positionId, params) {
|
|
3663
4194
|
return withErrorHandling(async () => {
|
|
3664
4195
|
const contractParams = {
|
|
@@ -3677,14 +4208,16 @@ async function closePosition(context, perpId, positionId, params) {
|
|
|
3677
4208
|
// Provide explicit gas limit to avoid estimation issues
|
|
3678
4209
|
});
|
|
3679
4210
|
const txHash = await context.walletClient.writeContract(request);
|
|
3680
|
-
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4211
|
+
const receipt = await context.publicClient.waitForTransactionReceipt({
|
|
4212
|
+
hash: txHash
|
|
4213
|
+
});
|
|
3681
4214
|
if (receipt.status === "reverted") {
|
|
3682
4215
|
throw new Error(`Transaction reverted. Hash: ${txHash}`);
|
|
3683
4216
|
}
|
|
3684
4217
|
let newPositionId = null;
|
|
3685
4218
|
for (const log of receipt.logs) {
|
|
3686
4219
|
try {
|
|
3687
|
-
const decoded =
|
|
4220
|
+
const decoded = (0, import_viem6.decodeEventLog)({
|
|
3688
4221
|
abi: PERP_MANAGER_ABI,
|
|
3689
4222
|
data: log.data,
|
|
3690
4223
|
topics: log.topics,
|
|
@@ -3727,9 +4260,10 @@ async function getPositionLiveDetailsFromContract(context, _perpId, positionId)
|
|
|
3727
4260
|
);
|
|
3728
4261
|
}
|
|
3729
4262
|
return {
|
|
3730
|
-
pnl: Number(
|
|
3731
|
-
|
|
3732
|
-
|
|
4263
|
+
pnl: Number((0, import_viem6.formatUnits)(pnl, 6)),
|
|
4264
|
+
// Negate so positive = user receives funding, matching quoteClosePosition convention
|
|
4265
|
+
fundingPayment: -Number((0, import_viem6.formatUnits)(funding, 6)),
|
|
4266
|
+
effectiveMargin: Number((0, import_viem6.formatUnits)(uint256ToInt256(netMargin), 6)),
|
|
3733
4267
|
isLiquidatable: wasLiquidated
|
|
3734
4268
|
};
|
|
3735
4269
|
}, `getPositionLiveDetailsFromContract for position ${positionId}`);
|
|
@@ -3773,6 +4307,34 @@ function calculateLiquidationPrice(rawData, isLong) {
|
|
|
3773
4307
|
return liqPrice;
|
|
3774
4308
|
}
|
|
3775
4309
|
}
|
|
4310
|
+
function calculatePnlPercentage(pnl, funding, margin) {
|
|
4311
|
+
const totalPnl = pnl + funding;
|
|
4312
|
+
const initialMargin = margin - totalPnl;
|
|
4313
|
+
if (initialMargin <= 0) return 0;
|
|
4314
|
+
return totalPnl / initialMargin * 100;
|
|
4315
|
+
}
|
|
4316
|
+
function calculateClosePositionParams(opts) {
|
|
4317
|
+
if (opts.isMaker) {
|
|
4318
|
+
return { minAmt0Out: 0, minAmt1Out: 0, maxAmt1In: 0 };
|
|
4319
|
+
}
|
|
4320
|
+
if (typeof opts.isLong !== "boolean") {
|
|
4321
|
+
throw new Error("isLong must be explicitly set for taker positions");
|
|
4322
|
+
}
|
|
4323
|
+
const absNotional = Math.abs(opts.notional);
|
|
4324
|
+
if (opts.isLong) {
|
|
4325
|
+
return {
|
|
4326
|
+
minAmt0Out: 0,
|
|
4327
|
+
minAmt1Out: Math.max(0, absNotional * (1 - opts.slippagePercent / 100)),
|
|
4328
|
+
maxAmt1In: 0
|
|
4329
|
+
};
|
|
4330
|
+
} else {
|
|
4331
|
+
return {
|
|
4332
|
+
minAmt0Out: 0,
|
|
4333
|
+
minAmt1Out: 0,
|
|
4334
|
+
maxAmt1In: absNotional * (1 + opts.slippagePercent / 100)
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
3776
4338
|
|
|
3777
4339
|
// src/functions/user.ts
|
|
3778
4340
|
function getUserUsdcBalance(userData) {
|
|
@@ -3784,13 +4346,21 @@ function getUserOpenPositions(userData) {
|
|
|
3784
4346
|
function getUserWalletAddress(userData) {
|
|
3785
4347
|
return userData.walletAddress;
|
|
3786
4348
|
}
|
|
3787
|
-
export
|
|
4349
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4350
|
+
0 && (module.exports = {
|
|
3788
4351
|
BEACON_ABI,
|
|
3789
4352
|
BIGINT_1E6,
|
|
3790
4353
|
ContractError,
|
|
4354
|
+
DEFAULT_MAKER_SLIPPAGE_TOLERANCE,
|
|
3791
4355
|
ErrorCategory,
|
|
3792
4356
|
ErrorSource,
|
|
4357
|
+
INT256_THRESHOLD,
|
|
3793
4358
|
InsufficientFundsError,
|
|
4359
|
+
MAX_PRICE,
|
|
4360
|
+
MAX_TICK,
|
|
4361
|
+
MAX_UINT128,
|
|
4362
|
+
MIN_PRICE,
|
|
4363
|
+
MIN_TICK,
|
|
3794
4364
|
NUMBER_1E6,
|
|
3795
4365
|
OpenPosition,
|
|
3796
4366
|
PERP_MANAGER_ABI,
|
|
@@ -3799,17 +4369,31 @@ export {
|
|
|
3799
4369
|
Q96,
|
|
3800
4370
|
RPCError,
|
|
3801
4371
|
TransactionRejectedError,
|
|
4372
|
+
UINT256_MAX,
|
|
3802
4373
|
ValidationError,
|
|
4374
|
+
adjustMargin,
|
|
4375
|
+
adjustNotional,
|
|
4376
|
+
applySlippage,
|
|
3803
4377
|
approveUsdc,
|
|
4378
|
+
calculateAlignedTicks,
|
|
4379
|
+
calculateClosePositionParams,
|
|
3804
4380
|
calculateEntryPrice,
|
|
3805
4381
|
calculateLeverage,
|
|
3806
4382
|
calculateLiquidationPrice,
|
|
3807
4383
|
calculateLiquidityForTargetRatio,
|
|
4384
|
+
calculatePnlPercentage,
|
|
3808
4385
|
calculatePositionSize,
|
|
3809
4386
|
calculatePositionValue,
|
|
3810
4387
|
closePosition,
|
|
4388
|
+
closePositionWithQuote,
|
|
4389
|
+
convertFundingDiffX96ToPercentPerPeriod,
|
|
4390
|
+
convertFundingPerSecondX96ToPercentPerDay,
|
|
4391
|
+
convertFundingPerSecondX96ToPercentPerMinute,
|
|
3811
4392
|
createPerp,
|
|
3812
4393
|
estimateLiquidity,
|
|
4394
|
+
getFundingRate,
|
|
4395
|
+
getIndexTWAP,
|
|
4396
|
+
getIndexValue,
|
|
3813
4397
|
getPerpBeacon,
|
|
3814
4398
|
getPerpBounds,
|
|
3815
4399
|
getPerpFees,
|
|
@@ -3826,6 +4410,7 @@ export {
|
|
|
3826
4410
|
getPositionPerpId,
|
|
3827
4411
|
getPositionPnl,
|
|
3828
4412
|
getRpcUrl,
|
|
4413
|
+
getUsdcAllowance,
|
|
3829
4414
|
getUserOpenPositions,
|
|
3830
4415
|
getUserUsdcBalance,
|
|
3831
4416
|
getUserWalletAddress,
|
|
@@ -3835,12 +4420,16 @@ export {
|
|
|
3835
4420
|
parseContractError,
|
|
3836
4421
|
priceToSqrtPriceX96,
|
|
3837
4422
|
priceToTick,
|
|
4423
|
+
quoteClosePosition,
|
|
4424
|
+
quoteOpenMakerPosition,
|
|
4425
|
+
quoteTakerPosition,
|
|
3838
4426
|
scale6Decimals,
|
|
3839
4427
|
scaleFrom6Decimals,
|
|
3840
4428
|
scaleFromX96,
|
|
3841
4429
|
scaleToX96,
|
|
3842
4430
|
sqrtPriceX96ToPrice,
|
|
3843
4431
|
tickToPrice,
|
|
4432
|
+
uint256ToInt256,
|
|
3844
4433
|
withErrorHandling
|
|
3845
|
-
};
|
|
3846
|
-
//# sourceMappingURL=index.
|
|
4434
|
+
});
|
|
4435
|
+
//# sourceMappingURL=index.cjs.map
|