@strobelabs/perpcity-sdk 0.5.1 → 0.6.1

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