@strobelabs/perpcity-sdk 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2375,6 +2375,10 @@ async function approveUsdc(context, amount, confirmations = DEFAULT_CONFIRMATION
2375
2375
  var NUMBER_1E6 = 1e6;
2376
2376
  var BIGINT_1E6 = 1000000n;
2377
2377
  var Q96 = 79228162514264337593543950336n;
2378
+ var MIN_TICK = -887272;
2379
+ var MAX_TICK = 887272;
2380
+ var MIN_PRICE = 1e-6;
2381
+ var MAX_PRICE = 1e6;
2378
2382
 
2379
2383
  // src/utils/conversions.ts
2380
2384
  function priceToSqrtPriceX96(price) {
@@ -2407,6 +2411,11 @@ function priceToTick(price, roundDown) {
2407
2411
  if (price <= 0) {
2408
2412
  throw new Error("Price must be positive");
2409
2413
  }
2414
+ if (price < MIN_PRICE || price > MAX_PRICE) {
2415
+ throw new Error(
2416
+ `Price ${price} is outside the representable range [${MIN_PRICE}, ${MAX_PRICE}]`
2417
+ );
2418
+ }
2410
2419
  const logPrice = Math.log(price) / Math.log(1.0001);
2411
2420
  return roundDown ? Math.floor(logPrice) : Math.ceil(logPrice);
2412
2421
  }
@@ -3468,7 +3477,9 @@ async function createPerp(context, params) {
3468
3477
  account: context.walletClient.account
3469
3478
  });
3470
3479
  const txHash = await context.walletClient.writeContract(request);
3471
- const receipt = await context.publicClient.waitForTransactionReceipt({ hash: txHash });
3480
+ const receipt = await context.publicClient.waitForTransactionReceipt({
3481
+ hash: txHash
3482
+ });
3472
3483
  if (receipt.status === "reverted") {
3473
3484
  throw new Error(`Transaction reverted. Hash: ${txHash}`);
3474
3485
  }
@@ -3535,7 +3546,9 @@ async function openTakerPosition(context, perpId, params) {
3535
3546
  account: context.walletClient.account
3536
3547
  });
3537
3548
  const txHash = await context.walletClient.writeContract(request);
3538
- const receipt = await context.publicClient.waitForTransactionReceipt({ hash: txHash });
3549
+ const receipt = await context.publicClient.waitForTransactionReceipt({
3550
+ hash: txHash
3551
+ });
3539
3552
  if (receipt.status === "reverted") {
3540
3553
  throw new Error(`Transaction reverted. Hash: ${txHash}`);
3541
3554
  }
@@ -3560,6 +3573,82 @@ async function openTakerPosition(context, perpId, params) {
3560
3573
  return new OpenPosition(context, perpId, takerPosId, params.isLong, false, txHash);
3561
3574
  }, "openTakerPosition");
3562
3575
  }
3576
+ function buildMakerContractParams(context, marginScaled, params, alignedTickLower, alignedTickUpper, maxAmt0In, maxAmt1In) {
3577
+ return {
3578
+ holder: context.walletClient.account.address,
3579
+ margin: marginScaled,
3580
+ liquidity: params.liquidity,
3581
+ tickLower: alignedTickLower,
3582
+ tickUpper: alignedTickUpper,
3583
+ maxAmt0In,
3584
+ maxAmt1In
3585
+ };
3586
+ }
3587
+ function calculateAlignedTicks(priceLower, priceUpper, tickSpacing) {
3588
+ const tickLower = priceToTick(priceLower, true);
3589
+ const tickUpper = priceToTick(priceUpper, false);
3590
+ const alignedTickLower = Math.floor(tickLower / tickSpacing) * tickSpacing;
3591
+ const alignedTickUpper = Math.ceil(tickUpper / tickSpacing) * tickSpacing;
3592
+ if (alignedTickLower < MIN_TICK) {
3593
+ throw new Error(
3594
+ `Lower tick ${alignedTickLower} is below MIN_TICK (${MIN_TICK}). Increase priceLower.`
3595
+ );
3596
+ }
3597
+ if (alignedTickUpper > MAX_TICK) {
3598
+ throw new Error(
3599
+ `Upper tick ${alignedTickUpper} exceeds MAX_TICK (${MAX_TICK}). Decrease priceUpper.`
3600
+ );
3601
+ }
3602
+ if (alignedTickLower === alignedTickUpper) {
3603
+ throw new Error(
3604
+ "Price range too narrow: lower and upper ticks are equal after alignment. Widen the range."
3605
+ );
3606
+ }
3607
+ return { alignedTickLower, alignedTickUpper };
3608
+ }
3609
+ function alignMakerTicks(params, tickSpacing) {
3610
+ return calculateAlignedTicks(params.priceLower, params.priceUpper, tickSpacing);
3611
+ }
3612
+ var DEFAULT_MAKER_SLIPPAGE_TOLERANCE = 0.01;
3613
+ var MAX_UINT128 = 2n ** 128n - 1n;
3614
+ async function quoteOpenMakerPosition(context, perpId, params) {
3615
+ return withErrorHandling(async () => {
3616
+ if (params.margin <= 0) {
3617
+ throw new Error("Margin must be greater than 0");
3618
+ }
3619
+ if (params.priceLower >= params.priceUpper) {
3620
+ throw new Error("priceLower must be less than priceUpper");
3621
+ }
3622
+ const marginScaled = scale6Decimals(params.margin);
3623
+ const perpData = await context.getPerpData(perpId);
3624
+ const { alignedTickLower, alignedTickUpper } = alignMakerTicks(params, perpData.tickSpacing);
3625
+ const contractParams = buildMakerContractParams(
3626
+ context,
3627
+ marginScaled,
3628
+ params,
3629
+ alignedTickLower,
3630
+ alignedTickUpper,
3631
+ MAX_UINT128,
3632
+ MAX_UINT128
3633
+ );
3634
+ const [unexpectedReason, perpDelta, usdDelta] = await context.publicClient.readContract({
3635
+ address: context.deployments().perpManager,
3636
+ abi: PERP_MANAGER_ABI,
3637
+ functionName: "quoteOpenMakerPosition",
3638
+ args: [perpId, contractParams]
3639
+ });
3640
+ if (unexpectedReason !== "0x") {
3641
+ throw new Error(`Quote failed: ${unexpectedReason}`);
3642
+ }
3643
+ return { perpDelta, usdDelta };
3644
+ }, "quoteOpenMakerPosition");
3645
+ }
3646
+ function applySlippage(delta, slippageTolerance) {
3647
+ if (delta >= 0n) return 0n;
3648
+ const absDelta = -delta;
3649
+ const slippageBps = BigInt(Math.ceil(slippageTolerance * 1e4));
3650
+ return absDelta + absDelta * slippageBps / 10000n;
3651
+ }
3563
3652
  async function openMakerPosition(context, perpId, params) {
3564
3653
  return withErrorHandling(async () => {
3565
3654
  if (params.margin <= 0) {
@@ -3569,8 +3658,24 @@ async function openMakerPosition(context, perpId, params) {
3569
3658
  throw new Error("priceLower must be less than priceUpper");
3570
3659
  }
3571
3660
  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);
3661
+ const perpData = await context.getPerpData(perpId);
3662
+ const { alignedTickLower, alignedTickUpper } = alignMakerTicks(params, perpData.tickSpacing);
3663
+ let maxAmt0In;
3664
+ let maxAmt1In;
3665
+ if (params.maxAmt0In === void 0 !== (params.maxAmt1In === void 0)) {
3666
+ throw new Error(
3667
+ "Both maxAmt0In and maxAmt1In must be provided together or neither. Omit both to use automatic quote-based slippage calculation."
3668
+ );
3669
+ }
3670
+ if (params.maxAmt0In !== void 0 && params.maxAmt1In !== void 0) {
3671
+ maxAmt0In = typeof params.maxAmt0In === "bigint" ? params.maxAmt0In : scale6Decimals(params.maxAmt0In);
3672
+ maxAmt1In = typeof params.maxAmt1In === "bigint" ? params.maxAmt1In : scale6Decimals(params.maxAmt1In);
3673
+ } else {
3674
+ const quote = await quoteOpenMakerPosition(context, perpId, params);
3675
+ const slippage = params.slippageTolerance ?? DEFAULT_MAKER_SLIPPAGE_TOLERANCE;
3676
+ maxAmt0In = applySlippage(quote.perpDelta, slippage);
3677
+ maxAmt1In = applySlippage(quote.usdDelta, slippage);
3678
+ }
3574
3679
  const currentAllowance = await context.publicClient.readContract({
3575
3680
  address: context.deployments().usdc,
3576
3681
  abi: erc20Abi3,
@@ -3581,21 +3686,15 @@ async function openMakerPosition(context, perpId, params) {
3581
3686
  if (currentAllowance < marginScaled) {
3582
3687
  await approveUsdc(context, marginScaled);
3583
3688
  }
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
- };
3689
+ const contractParams = buildMakerContractParams(
3690
+ context,
3691
+ marginScaled,
3692
+ params,
3693
+ alignedTickLower,
3694
+ alignedTickUpper,
3695
+ maxAmt0In,
3696
+ maxAmt1In
3697
+ );
3599
3698
  const { request } = await context.publicClient.simulateContract({
3600
3699
  address: context.deployments().perpManager,
3601
3700
  abi: PERP_MANAGER_ABI,
@@ -3604,7 +3703,9 @@ async function openMakerPosition(context, perpId, params) {
3604
3703
  account: context.walletClient.account
3605
3704
  });
3606
3705
  const txHash = await context.walletClient.writeContract(request);
3607
- const receipt = await context.publicClient.waitForTransactionReceipt({ hash: txHash });
3706
+ const receipt = await context.publicClient.waitForTransactionReceipt({
3707
+ hash: txHash
3708
+ });
3608
3709
  if (receipt.status === "reverted") {
3609
3710
  throw new Error(`Transaction reverted. Hash: ${txHash}`);
3610
3711
  }
@@ -3791,6 +3892,10 @@ export {
3791
3892
  ErrorCategory,
3792
3893
  ErrorSource,
3793
3894
  InsufficientFundsError,
3895
+ MAX_PRICE,
3896
+ MAX_TICK,
3897
+ MIN_PRICE,
3898
+ MIN_TICK,
3794
3899
  NUMBER_1E6,
3795
3900
  OpenPosition,
3796
3901
  PERP_MANAGER_ABI,
@@ -3801,6 +3906,7 @@ export {
3801
3906
  TransactionRejectedError,
3802
3907
  ValidationError,
3803
3908
  approveUsdc,
3909
+ calculateAlignedTicks,
3804
3910
  calculateEntryPrice,
3805
3911
  calculateLeverage,
3806
3912
  calculateLiquidationPrice,
@@ -3835,6 +3941,7 @@ export {
3835
3941
  parseContractError,
3836
3942
  priceToSqrtPriceX96,
3837
3943
  priceToTick,
3944
+ quoteOpenMakerPosition,
3838
3945
  scale6Decimals,
3839
3946
  scaleFrom6Decimals,
3840
3947
  scaleFromX96,