@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.
@@ -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: "int256",
1898
+ internalType: "uint256",
1781
1899
  name: "netMargin",
1782
- type: "int256"
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
- import TTLCache from "@isaacs/ttlcache";
2270
- import {
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
- import { erc20Abi } from "viem";
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
- import { BaseError, ContractFunctionRevertedError } from "viem";
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: `Minimum amount not met. Required: ${args[0]}, Received: ${args[1]}`,
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 TTLCache({ ttl: 5 * 60 * 1e3 });
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
- const sqrtPrice = Math.sqrt(markPrice);
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: erc20Abi2,
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
- fundingPayment: Number(formatUnits(funding, 6)),
3253
- effectiveMargin: Number(formatUnits(netMargin, 6)),
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
- import { decodeEventLog, formatUnits as formatUnits2 } from "viem";
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(formatUnits2(pnl, 6)),
3419
- fundingPayment: Number(formatUnits2(funding, 6)),
3420
- effectiveMargin: Number(formatUnits2(netMargin, 6)),
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
- import { decodeEventLog as decodeEventLog2, erc20Abi as erc20Abi3 } from "viem";
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({ hash: txHash });
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 = decodeEventLog2({
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: erc20Abi3,
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({ hash: txHash });
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 = decodeEventLog2({
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 maxAmt0InScaled = typeof params.maxAmt0In === "bigint" ? params.maxAmt0In : scale6Decimals(params.maxAmt0In);
3573
- const maxAmt1InScaled = typeof params.maxAmt1In === "bigint" ? params.maxAmt1In : scale6Decimals(params.maxAmt1In);
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: erc20Abi3,
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 perpData = await context.getPerpData(perpId);
3585
- const tickLower = priceToTick(params.priceLower, true);
3586
- const tickUpper = priceToTick(params.priceUpper, false);
3587
- const tickSpacing = perpData.tickSpacing;
3588
- const alignedTickLower = Math.floor(tickLower / tickSpacing) * tickSpacing;
3589
- const alignedTickUpper = Math.ceil(tickUpper / tickSpacing) * tickSpacing;
3590
- const contractParams = {
3591
- holder: context.walletClient.account.address,
3592
- margin: marginScaled,
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({ hash: txHash });
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 = decodeEventLog2({
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
- import { decodeEventLog as decodeEventLog3, formatUnits as formatUnits3 } from "viem";
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({ hash: txHash });
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 = decodeEventLog3({
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(formatUnits3(pnl, 6)),
3731
- fundingPayment: Number(formatUnits3(funding, 6)),
3732
- effectiveMargin: Number(formatUnits3(netMargin, 6)),
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.mjs.map
4434
+ });
4435
+ //# sourceMappingURL=index.cjs.map