@strobelabs/perpcity-sdk 0.5.1 → 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
@@ -2379,6 +2496,8 @@ var MIN_TICK = -887272;
2379
2496
  var MAX_TICK = 887272;
2380
2497
  var MIN_PRICE = 1e-6;
2381
2498
  var MAX_PRICE = 1e6;
2499
+ var UINT256_MAX = (1n << 256n) - 1n;
2500
+ var INT256_THRESHOLD = 1n << 255n;
2382
2501
 
2383
2502
  // src/utils/conversions.ts
2384
2503
  function priceToSqrtPriceX96(price) {
@@ -2422,6 +2541,12 @@ function priceToTick(price, roundDown) {
2422
2541
  function tickToPrice(tick) {
2423
2542
  return 1.0001 ** tick;
2424
2543
  }
2544
+ function uint256ToInt256(value) {
2545
+ if (value >= INT256_THRESHOLD) {
2546
+ return value - UINT256_MAX - 1n;
2547
+ }
2548
+ return value;
2549
+ }
2425
2550
  function sqrtPriceX96ToPrice(sqrtPriceX96) {
2426
2551
  const priceX96 = sqrtPriceX96 * sqrtPriceX96 / Q96;
2427
2552
  return scaleFromX96(priceX96);
@@ -2437,7 +2562,7 @@ function scaleFrom6Decimals(value) {
2437
2562
  }
2438
2563
 
2439
2564
  // src/utils/errors.ts
2440
- import { BaseError, ContractFunctionRevertedError } from "viem";
2565
+ var import_viem2 = require("viem");
2441
2566
  var ErrorCategory = /* @__PURE__ */ ((ErrorCategory2) => {
2442
2567
  ErrorCategory2["USER_ERROR"] = "USER_ERROR";
2443
2568
  ErrorCategory2["STATE_ERROR"] = "STATE_ERROR";
@@ -2495,9 +2620,9 @@ function parseContractError(error) {
2495
2620
  if (error instanceof PerpCityError) {
2496
2621
  return error;
2497
2622
  }
2498
- if (error instanceof BaseError) {
2499
- const revertError = error.walk((err) => err instanceof ContractFunctionRevertedError);
2500
- 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) {
2501
2626
  const errorName = revertError.data?.errorName ?? "Unknown";
2502
2627
  const args = revertError.data?.args ?? [];
2503
2628
  const { message, debug } = formatContractError(errorName, args);
@@ -2662,7 +2787,7 @@ function formatContractError(errorName, args) {
2662
2787
  };
2663
2788
  case "MinimumAmountInsufficient":
2664
2789
  return {
2665
- 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.",
2666
2791
  debug: { source, category: "USER_ERROR" /* USER_ERROR */ }
2667
2792
  };
2668
2793
  case "PriceImpactTooHigh":
@@ -2941,6 +3066,23 @@ async function withErrorHandling(fn, context) {
2941
3066
  }
2942
3067
  }
2943
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
+
2944
3086
  // src/utils/liquidity.ts
2945
3087
  async function estimateLiquidity(_context, tickLower, tickUpper, usdScaled) {
2946
3088
  if (tickLower >= tickUpper) {
@@ -3043,16 +3185,16 @@ function getRpcUrl(config = {}) {
3043
3185
  // src/context.ts
3044
3186
  var PerpCityContext = class {
3045
3187
  constructor(config) {
3046
- this.configCache = new TTLCache({ ttl: 5 * 60 * 1e3 });
3188
+ this.configCache = new import_ttlcache.default({ ttl: 5 * 60 * 1e3 });
3047
3189
  this.walletClient = config.walletClient;
3048
3190
  if (!config.walletClient.chain?.id) {
3049
3191
  throw new Error(
3050
3192
  "PerpCityContext: walletClient.chain must be defined with a numeric id. Ensure your walletClient was created with a chain parameter."
3051
3193
  );
3052
3194
  }
3053
- this.publicClient = createPublicClient({
3195
+ this.publicClient = (0, import_viem3.createPublicClient)({
3054
3196
  chain: config.walletClient.chain,
3055
- transport: http(config.rpcUrl, { batch: true })
3197
+ transport: (0, import_viem3.http)(config.rpcUrl, { batch: true })
3056
3198
  });
3057
3199
  this._deployments = config.deployments;
3058
3200
  }
@@ -3134,8 +3276,7 @@ var PerpCityContext = class {
3134
3276
  const tickSpacing = Number(cfg.key.tickSpacing);
3135
3277
  let sqrtPriceX96;
3136
3278
  if (markPrice) {
3137
- const sqrtPrice = Math.sqrt(markPrice);
3138
- sqrtPriceX96 = BigInt(Math.floor(sqrtPrice * 2 ** 96));
3279
+ sqrtPriceX96 = priceToSqrtPriceX96(markPrice);
3139
3280
  } else {
3140
3281
  sqrtPriceX96 = await this.publicClient.readContract({
3141
3282
  address: this.deployments().perpManager,
@@ -3220,7 +3361,7 @@ var PerpCityContext = class {
3220
3361
  async fetchUserData(userAddress, positions) {
3221
3362
  const usdcBalance = await this.publicClient.readContract({
3222
3363
  address: this.deployments().usdc,
3223
- abi: erc20Abi2,
3364
+ abi: import_viem3.erc20Abi,
3224
3365
  functionName: "balanceOf",
3225
3366
  args: [userAddress]
3226
3367
  });
@@ -3238,7 +3379,7 @@ var PerpCityContext = class {
3238
3379
  );
3239
3380
  return {
3240
3381
  walletAddress: userAddress,
3241
- usdcBalance: Number(formatUnits(usdcBalance, 6)),
3382
+ usdcBalance: Number((0, import_viem3.formatUnits)(usdcBalance, 6)),
3242
3383
  openPositions: openPositionsData
3243
3384
  };
3244
3385
  }
@@ -3257,9 +3398,10 @@ var PerpCityContext = class {
3257
3398
  );
3258
3399
  }
3259
3400
  return {
3260
- pnl: Number(formatUnits(pnl, 6)),
3261
- fundingPayment: Number(formatUnits(funding, 6)),
3262
- 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)),
3263
3405
  isLiquidatable: wasLiquidated
3264
3406
  };
3265
3407
  }, `fetchPositionLiveDetailsFromContract for position ${positionId}`);
@@ -3303,29 +3445,35 @@ var PerpCityContext = class {
3303
3445
  args: [positionId]
3304
3446
  });
3305
3447
  const resultArray = result;
3306
- const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios] = resultArray;
3448
+ const [perpId, margin, entryPerpDelta, entryUsdDelta, , , , marginRatios, makerDetailsRaw] = resultArray;
3307
3449
  const zeroPerpId = `0x${"0".repeat(64)}`;
3308
3450
  if (perpId === zeroPerpId) {
3309
3451
  throw new Error(`Position ${positionId} does not exist`);
3310
3452
  }
3453
+ const isMaker = makerDetailsRaw != null && (makerDetailsRaw.tickLower !== 0 || makerDetailsRaw.tickUpper !== 0);
3311
3454
  return {
3312
3455
  perpId,
3313
3456
  positionId,
3314
- margin: Number(formatUnits(margin, 6)),
3457
+ margin: Number((0, import_viem3.formatUnits)(margin, 6)),
3315
3458
  entryPerpDelta,
3316
3459
  entryUsdDelta,
3317
3460
  marginRatios: {
3318
3461
  min: Number(marginRatios.min),
3319
3462
  max: Number(marginRatios.max),
3320
3463
  liq: Number(marginRatios.liq)
3321
- }
3464
+ },
3465
+ makerDetails: isMaker ? {
3466
+ unlockTimestamp: Number(makerDetailsRaw.unlockTimestamp),
3467
+ tickLower: makerDetailsRaw.tickLower,
3468
+ tickUpper: makerDetailsRaw.tickUpper
3469
+ } : null
3322
3470
  };
3323
3471
  }, `getPositionRawData for position ${positionId}`);
3324
3472
  }
3325
3473
  };
3326
3474
 
3327
3475
  // src/functions/open-position.ts
3328
- import { decodeEventLog, formatUnits as formatUnits2 } from "viem";
3476
+ var import_viem4 = require("viem");
3329
3477
  var OpenPosition = class _OpenPosition {
3330
3478
  constructor(context, perpId, positionId, isLong, isMaker, txHash) {
3331
3479
  this.context = context;
@@ -3362,7 +3510,7 @@ var OpenPosition = class _OpenPosition {
3362
3510
  let _wasFullyClosed = false;
3363
3511
  for (const log of receipt.logs) {
3364
3512
  try {
3365
- const openedDecoded = decodeEventLog({
3513
+ const openedDecoded = (0, import_viem4.decodeEventLog)({
3366
3514
  abi: PERP_MANAGER_ABI,
3367
3515
  data: log.data,
3368
3516
  topics: log.topics,
@@ -3376,7 +3524,7 @@ var OpenPosition = class _OpenPosition {
3376
3524
  }
3377
3525
  } catch (_e) {
3378
3526
  try {
3379
- const closedDecoded = decodeEventLog({
3527
+ const closedDecoded = (0, import_viem4.decodeEventLog)({
3380
3528
  abi: PERP_MANAGER_ABI,
3381
3529
  data: log.data,
3382
3530
  topics: log.topics,
@@ -3424,9 +3572,9 @@ var OpenPosition = class _OpenPosition {
3424
3572
  );
3425
3573
  }
3426
3574
  return {
3427
- pnl: Number(formatUnits2(pnl, 6)),
3428
- fundingPayment: Number(formatUnits2(funding, 6)),
3429
- 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)),
3430
3578
  isLiquidatable: wasLiquidated
3431
3579
  };
3432
3580
  }, `liveDetails for position ${this.positionId}`);
@@ -3434,6 +3582,8 @@ var OpenPosition = class _OpenPosition {
3434
3582
  };
3435
3583
 
3436
3584
  // src/functions/perp.ts
3585
+ var TWAVG_WINDOW = 3600;
3586
+ var INTERVAL = 86400n;
3437
3587
  function getPerpMark(perpData) {
3438
3588
  return perpData.mark;
3439
3589
  }
@@ -3449,9 +3599,58 @@ function getPerpFees(perpData) {
3449
3599
  function getPerpTickSpacing(perpData) {
3450
3600
  return perpData.tickSpacing;
3451
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
+ }
3452
3651
 
3453
3652
  // src/functions/perp-manager.ts
3454
- import { decodeEventLog as decodeEventLog2, erc20Abi as erc20Abi3 } from "viem";
3653
+ var import_viem5 = require("viem");
3455
3654
  async function createPerp(context, params) {
3456
3655
  return withErrorHandling(async () => {
3457
3656
  const deployments = context.deployments();
@@ -3485,7 +3684,7 @@ async function createPerp(context, params) {
3485
3684
  }
3486
3685
  for (const log of receipt.logs) {
3487
3686
  try {
3488
- const decoded = decodeEventLog2({
3687
+ const decoded = (0, import_viem5.decodeEventLog)({
3489
3688
  abi: PERP_MANAGER_ABI,
3490
3689
  data: log.data,
3491
3690
  topics: log.topics,
@@ -3522,7 +3721,7 @@ async function openTakerPosition(context, perpId, params) {
3522
3721
  const requiredAmount = marginScaled + totalFees;
3523
3722
  const currentAllowance = await context.publicClient.readContract({
3524
3723
  address: context.deployments().usdc,
3525
- abi: erc20Abi3,
3724
+ abi: import_viem5.erc20Abi,
3526
3725
  functionName: "allowance",
3527
3726
  args: [context.walletClient.account.address, context.deployments().perpManager],
3528
3727
  blockTag: "latest"
@@ -3555,7 +3754,7 @@ async function openTakerPosition(context, perpId, params) {
3555
3754
  let takerPosId = null;
3556
3755
  for (const log of receipt.logs) {
3557
3756
  try {
3558
- const decoded = decodeEventLog2({
3757
+ const decoded = (0, import_viem5.decodeEventLog)({
3559
3758
  abi: PERP_MANAGER_ABI,
3560
3759
  data: log.data,
3561
3760
  topics: log.topics
@@ -3643,11 +3842,17 @@ async function quoteOpenMakerPosition(context, perpId, params) {
3643
3842
  return { perpDelta, usdDelta };
3644
3843
  }, "quoteOpenMakerPosition");
3645
3844
  }
3646
- function applySlippage(delta, slippageTolerance) {
3647
- if (delta >= 0n) return 0n;
3648
- const absDelta = -delta;
3845
+ function applySlippage(delta, slippageTolerance, fallbackRef) {
3649
3846
  const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
3650
- return absDelta + absDelta * slippageBps / 10000n;
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;
3651
3856
  }
3652
3857
  async function openMakerPosition(context, perpId, params) {
3653
3858
  return withErrorHandling(async () => {
@@ -3673,12 +3878,12 @@ async function openMakerPosition(context, perpId, params) {
3673
3878
  } else {
3674
3879
  const quote = await quoteOpenMakerPosition(context, perpId, params);
3675
3880
  const slippage = params.slippageTolerance ?? DEFAULT_MAKER_SLIPPAGE_TOLERANCE;
3676
- maxAmt0In = applySlippage(quote.perpDelta, slippage);
3677
- maxAmt1In = applySlippage(quote.usdDelta, slippage);
3881
+ maxAmt0In = applySlippage(quote.perpDelta, slippage, quote.usdDelta);
3882
+ maxAmt1In = applySlippage(quote.usdDelta, slippage, quote.perpDelta);
3678
3883
  }
3679
3884
  const currentAllowance = await context.publicClient.readContract({
3680
3885
  address: context.deployments().usdc,
3681
- abi: erc20Abi3,
3886
+ abi: import_viem5.erc20Abi,
3682
3887
  functionName: "allowance",
3683
3888
  args: [context.walletClient.account.address, context.deployments().perpManager],
3684
3889
  blockTag: "latest"
@@ -3712,7 +3917,7 @@ async function openMakerPosition(context, perpId, params) {
3712
3917
  let makerPosId = null;
3713
3918
  for (const log of receipt.logs) {
3714
3919
  try {
3715
- const decoded = decodeEventLog2({
3920
+ const decoded = (0, import_viem5.decodeEventLog)({
3716
3921
  abi: PERP_MANAGER_ABI,
3717
3922
  data: log.data,
3718
3923
  topics: log.topics
@@ -3730,9 +3935,109 @@ async function openMakerPosition(context, perpId, params) {
3730
3935
  return new OpenPosition(context, perpId, makerPosId, void 0, true, txHash);
3731
3936
  }, "openMakerPosition");
3732
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
+ }
3733
4038
 
3734
4039
  // src/functions/position.ts
3735
- import { decodeEventLog as decodeEventLog3, formatUnits as formatUnits3 } from "viem";
4040
+ var import_viem6 = require("viem");
3736
4041
  function getPositionPerpId(positionData) {
3737
4042
  return positionData.perpId;
3738
4043
  }
@@ -3760,6 +4065,131 @@ function getPositionEffectiveMargin(positionData) {
3760
4065
  function getPositionIsLiquidatable(positionData) {
3761
4066
  return positionData.liveDetails.isLiquidatable;
3762
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
+ }
3763
4193
  async function closePosition(context, perpId, positionId, params) {
3764
4194
  return withErrorHandling(async () => {
3765
4195
  const contractParams = {
@@ -3778,14 +4208,16 @@ async function closePosition(context, perpId, positionId, params) {
3778
4208
  // Provide explicit gas limit to avoid estimation issues
3779
4209
  });
3780
4210
  const txHash = await context.walletClient.writeContract(request);
3781
- const receipt = await context.publicClient.waitForTransactionReceipt({ hash: txHash });
4211
+ const receipt = await context.publicClient.waitForTransactionReceipt({
4212
+ hash: txHash
4213
+ });
3782
4214
  if (receipt.status === "reverted") {
3783
4215
  throw new Error(`Transaction reverted. Hash: ${txHash}`);
3784
4216
  }
3785
4217
  let newPositionId = null;
3786
4218
  for (const log of receipt.logs) {
3787
4219
  try {
3788
- const decoded = decodeEventLog3({
4220
+ const decoded = (0, import_viem6.decodeEventLog)({
3789
4221
  abi: PERP_MANAGER_ABI,
3790
4222
  data: log.data,
3791
4223
  topics: log.topics,
@@ -3828,9 +4260,10 @@ async function getPositionLiveDetailsFromContract(context, _perpId, positionId)
3828
4260
  );
3829
4261
  }
3830
4262
  return {
3831
- pnl: Number(formatUnits3(pnl, 6)),
3832
- fundingPayment: Number(formatUnits3(funding, 6)),
3833
- 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)),
3834
4267
  isLiquidatable: wasLiquidated
3835
4268
  };
3836
4269
  }, `getPositionLiveDetailsFromContract for position ${positionId}`);
@@ -3874,6 +4307,34 @@ function calculateLiquidationPrice(rawData, isLong) {
3874
4307
  return liqPrice;
3875
4308
  }
3876
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
+ }
3877
4338
 
3878
4339
  // src/functions/user.ts
3879
4340
  function getUserUsdcBalance(userData) {
@@ -3885,15 +4346,19 @@ function getUserOpenPositions(userData) {
3885
4346
  function getUserWalletAddress(userData) {
3886
4347
  return userData.walletAddress;
3887
4348
  }
3888
- export {
4349
+ // Annotate the CommonJS export names for ESM import in node:
4350
+ 0 && (module.exports = {
3889
4351
  BEACON_ABI,
3890
4352
  BIGINT_1E6,
3891
4353
  ContractError,
4354
+ DEFAULT_MAKER_SLIPPAGE_TOLERANCE,
3892
4355
  ErrorCategory,
3893
4356
  ErrorSource,
4357
+ INT256_THRESHOLD,
3894
4358
  InsufficientFundsError,
3895
4359
  MAX_PRICE,
3896
4360
  MAX_TICK,
4361
+ MAX_UINT128,
3897
4362
  MIN_PRICE,
3898
4363
  MIN_TICK,
3899
4364
  NUMBER_1E6,
@@ -3904,18 +4369,31 @@ export {
3904
4369
  Q96,
3905
4370
  RPCError,
3906
4371
  TransactionRejectedError,
4372
+ UINT256_MAX,
3907
4373
  ValidationError,
4374
+ adjustMargin,
4375
+ adjustNotional,
4376
+ applySlippage,
3908
4377
  approveUsdc,
3909
4378
  calculateAlignedTicks,
4379
+ calculateClosePositionParams,
3910
4380
  calculateEntryPrice,
3911
4381
  calculateLeverage,
3912
4382
  calculateLiquidationPrice,
3913
4383
  calculateLiquidityForTargetRatio,
4384
+ calculatePnlPercentage,
3914
4385
  calculatePositionSize,
3915
4386
  calculatePositionValue,
3916
4387
  closePosition,
4388
+ closePositionWithQuote,
4389
+ convertFundingDiffX96ToPercentPerPeriod,
4390
+ convertFundingPerSecondX96ToPercentPerDay,
4391
+ convertFundingPerSecondX96ToPercentPerMinute,
3917
4392
  createPerp,
3918
4393
  estimateLiquidity,
4394
+ getFundingRate,
4395
+ getIndexTWAP,
4396
+ getIndexValue,
3919
4397
  getPerpBeacon,
3920
4398
  getPerpBounds,
3921
4399
  getPerpFees,
@@ -3932,6 +4410,7 @@ export {
3932
4410
  getPositionPerpId,
3933
4411
  getPositionPnl,
3934
4412
  getRpcUrl,
4413
+ getUsdcAllowance,
3935
4414
  getUserOpenPositions,
3936
4415
  getUserUsdcBalance,
3937
4416
  getUserWalletAddress,
@@ -3941,13 +4420,16 @@ export {
3941
4420
  parseContractError,
3942
4421
  priceToSqrtPriceX96,
3943
4422
  priceToTick,
4423
+ quoteClosePosition,
3944
4424
  quoteOpenMakerPosition,
4425
+ quoteTakerPosition,
3945
4426
  scale6Decimals,
3946
4427
  scaleFrom6Decimals,
3947
4428
  scaleFromX96,
3948
4429
  scaleToX96,
3949
4430
  sqrtPriceX96ToPrice,
3950
4431
  tickToPrice,
4432
+ uint256ToInt256,
3951
4433
  withErrorHandling
3952
- };
3953
- //# sourceMappingURL=index.mjs.map
4434
+ });
4435
+ //# sourceMappingURL=index.cjs.map