Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion typescript/swap-vm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1inch/swap-vm-sdk",
"version": "0.1.2-rc.7",
"version": "0.1.3",
"description": "1inch Swap VM SDK",
"author": "@1inch",
"license": "LicenseRef-Degensoft-SwapVM-1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { HexString } from '@1inch/sdk-core'
import { UINT_256_MAX } from '@1inch/byte-utils'
import assert from 'assert'
import { ConcentrateGrowLiquidity2DArgsCoder } from './concentrate-grow-liquidity-2d-args-coder'
import { bigintSqrt } from './bigint-sqrt'
import { bigintSqrt } from '../utils/bigint-sqrt'
import type { IArgsCoder, IArgsData } from '../types'

export const ONE_E18: bigint = 10n ** 18n
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
computeLiquidityFromAmounts,
} from './concentrate-liquidity-math'
import { ONE_E18 } from '../concentrate-grow-liquidity-2d-args'
import { bigintSqrt } from '../bigint-sqrt'
import { bigintSqrt } from '../../utils/bigint-sqrt'

describe('concentrate-liquidity-math', () => {
describe('computeLiquidityAndPrice', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1

import { UINT_256_MAX } from '@1inch/byte-utils'
import { bigintSqrt } from '../bigint-sqrt'
import { bigintSqrt } from '../../utils/bigint-sqrt'

const ONE = 10n ** 18n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ export type {
PriceBounds,
ConcentratedLiquidityInfo,
} from './concentrate-liquidity-calculator/types'
export * from './bigint-sqrt'
export { bigintSqrt } from '../utils/bigint-sqrt'
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1

export { Price } from './price'
export { truncateHumanDecimalString } from './truncate-human-decimal-string'
export { truncateHumanDecimalString } from '../../utils/truncate-human-decimal-string'
export type { PriceJSON, PricePair, PriceToken } from './types'
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Address } from '@1inch/sdk-core'
import { formatUnits, parseUnits } from 'viem'
import assert from 'assert'
import type { PriceJSON, PricePair, PriceToken } from './types'
import { truncateHumanDecimalString } from './truncate-human-decimal-string'
import { bigintSqrt } from '../bigint-sqrt'
import { bigintSqrt } from '../../utils/bigint-sqrt'
import { truncateHumanDecimalString } from '../../utils/truncate-human-decimal-string'

const ONE_E18 = 10n ** 18n

Expand Down
1 change: 1 addition & 0 deletions typescript/swap-vm/src/swap-vm/instructions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { IArgsData } from './types'
import * as peggedSwap from './pegged-swap'

export * from './types'
export { bigintSqrt, truncateHumanDecimalString } from './utils'
export { EMPTY_OPCODE } from './empty'
export * as balances from './balances'
export * as controls from './controls'
Expand Down
10 changes: 10 additions & 0 deletions typescript/swap-vm/src/swap-vm/instructions/pegged-swap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@
export * from './opcodes'
export { PeggedSwapArgs } from './pegged-swap-args'
export type { PeggedTokenInfo } from './types'
export { PeggedSwapCalculator } from './pegged-swap-calculator'
export { PeggedPrice } from './price'
export type { PeggedInitialBalances, PeggedSwapCalculatorArgs } from './pegged-swap-calculator'
export type {
PeggedPriceJSON,
PeggedPricePair,
PeggedReservesInput,
PeggedTokenRef,
PeggedTokenReserve,
} from './price'
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# PeggedSwapCalculator

Helper for **initial pool balances before deployment** of a PeggedSwap strategy (`peggedSwapGrowPriceRange2D`). It answers: “I know the target spot price and how much of one token I want to deposit — how much of the other token do I need so the pool starts on-peg?”

This calculator only covers the **pre-launch** case where `currentReserve = initialReserve` (normalized coordinates `u = v = 1` on the curve). It does not model reserves after trading or off-center spot.

## Overview

- **Token ordering**: Lt = lower address, Gt = higher address (same as on-chain `PeggedSwapMath`). `tokenA` / `tokenB` order in the constructor does not matter; use `tokenLt` / `tokenGt` getters if needed.
- **Price convention**: Use `PeggedPrice` — human string is **quote per 1 base** (same as `Price` in concentrate). Internally the marginal rate is **tokenGt per tokenLt** in 1e18 fixed-point.
- **At deployment center**: Spot does not depend on `linearWidth`; with equal normalization, `reserveGt ≈ reserveLt × spot` (in raw units, via `toGtPerLtE18()`). `linearWidth` still goes into `PeggedSwapArgs` and affects swaps after launch.

## On-chain curve (context)

PeggedSwap invariant (see [PeggedSwap.sol](https://github.com/1inch/swap-vm/blob/main/src/instructions/PeggedSwap.sol)):

```
√(x/X₀) + √(y/Y₀) + A(x/X₀ + y/Y₀) = 1 + A
```

`X₀` / `Y₀` in args are **rate-scaled** initial balances: `x0 = initialLt × rateLt`, `y0 = initialGt × rateGt` (see `PeggedSwapArgs.fromTokens`).

## Types

### `PeggedSwapCalculatorArgs`

| Field | Type | Description |
|-------|------|-------------|
| `tokenA` | `PeggedTokenRef` | First token (`address`, `decimals`). |
| `tokenB` | `PeggedTokenRef` | Second token. |

### `PeggedInitialBalances`

| Field | Type | Description |
|-------|------|-------------|
| `reserveLt` | `bigint` | Raw initial balance for Lt (lower address). |
| `reserveGt` | `bigint` | Raw initial balance for Gt (higher address). |

## API

Exported from `instructions.peggedSwap` on `@1inch/swap-vm-sdk`:

```ts
import { instructions } from '@1inch/swap-vm-sdk'
import { parseUnits } from 'viem'

const { PeggedSwapCalculator, PeggedPrice, PeggedSwapArgs } = instructions.peggedSwap
```

### Creating a calculator

```ts
const USDC = { address: usdcAddress, decimals: 6 }
const USDT = { address: usdtAddress, decimals: 6 }

const calculator = PeggedSwapCalculator.new({ tokenA: USDC, tokenB: USDT })
// calculator.tokenLt → USDC (lower address)
// calculator.tokenGt → USDT
```

### `computeFixedAllocation(spotPrice, fixedReserveForToken, fixedReserve)`

Given a target **`PeggedPrice`** and a **raw** amount for one token, returns both initial reserves in Lt/Gt order.

**Use case**: “Deposit 1M USDC; how much USDT at 0.998 USDT/USDC before the strategy ships?”

```ts
const spot = PeggedPrice.fromHuman('0.998', {
quoteToken: { address: usdtAddress, decimals: 6 },
baseToken: { address: usdcAddress, decimals: 6 },
})

const fixedUsdc = parseUnits('1000000', 6)
const balances = calculator.computeFixedAllocation(spot, usdcAddress, fixedUsdc)

// balances.reserveLt — USDC amount (raw)
// balances.reserveGt — USDT amount (raw)
```

Fix the other side instead:

```ts
const balances = calculator.computeFixedAllocation(spot, usdtAddress, parseUnits('998000', 6))
```

### Building `PeggedSwapArgs`

Pass the computed raw reserves into `PeggedSwapArgs.fromTokens` (rates and Lt/Gt ordering are applied inside):

```ts
const linearWidth = 8n * 10n ** 26n // A = 0.8

const args = PeggedSwapArgs.fromTokens(
{ address: usdcAddress, decimals: 6, reserve: balances.reserveLt },
{ address: usdtAddress, decimals: 6, reserve: balances.reserveGt },
linearWidth,
)
```

Use `args` with `peggedSwapGrowPriceRange2D` in your program builder (e.g. `AquaPeggedAmmStrategy`).

## Spot price helpers (`PeggedPrice`)

The calculator does not compute spot from reserves; use **`PeggedPrice`**:

| Method | Purpose |
|--------|---------|
| `PeggedPrice.fromHuman(price, pair)` | Parse target quote-per-base (e.g. `'0.998'`, `'1.002'`). |
| `PeggedPrice.fromReserves({ reserveA, reserveB, linearWidth })` | Marginal spot from initial/current reserves; set `currentReserve = initialReserve` before launch. |
| `price.toHuman(quoteToken)` | Format for display. |

**Verify** allocation (optional):

```ts
const check = PeggedPrice.fromReserves({
linearWidth,
reserveA: { ...usdc, initialReserve: balances.reserveLt, currentReserve: balances.reserveLt },
reserveB: { ...usdt, initialReserve: balances.reserveGt, currentReserve: balances.reserveGt },
})

expect(check.toHuman(usdtAddress)).toBe('0.998')
```

## Examples

### USDC / USDT @ 0.998

Lt = USDC, Gt = USDT. Quote = USDT, base = USDC → 0.998 USDT per 1 USDC.

1M USDC fixed → 998_000 USDT (6 decimals each).

### DAI / USDC @ 1.002

Lt = DAI (18 dec), Gt = USDC (6 dec). Quote = USDC, base = DAI → 1.002 USDC per 1 DAI.

1M DAI fixed → 1_002_000 USDC (raw `6` decimals).

## Relation to SwapVM

| Piece | Role |
|-------|------|
| `PeggedSwapCalculator` | Initial Lt/Gt raw balances from spot + one fixed leg. |
| `PeggedSwapArgs` | Encoded `x0`, `y0`, `linearWidth`, `rateLt`, `rateGt` for the instruction. |
| `PeggedPrice` | Human and reserve-based marginal price (gt per lt). |

After deployment, spot moves with reserves; use `PeggedPrice.fromReserves` with **current** balances, not this calculator.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1

export { PeggedSwapCalculator } from './pegged-swap-calculator'
export type { PeggedInitialBalances, PeggedSwapCalculatorArgs } from './types'
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1

import { describe, expect, it } from 'vitest'
import { Address } from '@1inch/sdk-core'
import { PeggedSwapCalculator } from './pegged-swap-calculator'
import { PeggedPrice } from '../price/pegged-price'
import type { PeggedPricePair } from '../price/types'

const TOKEN_A = new Address('0x0000000000000000000000000000000000000001')
const TOKEN_B = new Address('0x0000000000000000000000000000000000000002')
const LINEAR_WIDTH = 8n * 10n ** 26n

const USDC = new Address('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
const USDT = new Address('0xdAC17F958D2ee523a2206206994597C13D831ec7')
const DAI = new Address('0x6B175474E89094C44Da98b954EedeAC495271d0F')

const pairGtQuoteLtBase: PeggedPricePair = {
quoteToken: { address: TOKEN_B, decimals: 18 },
baseToken: { address: TOKEN_A, decimals: 18 },
}

describe('PeggedSwapCalculator', () => {
const calculator = PeggedSwapCalculator.new({
tokenA: { address: TOKEN_A, decimals: 18 },
tokenB: { address: TOKEN_B, decimals: 18 },
})

it('computeFixedAllocation derives initial Gt from fixed Lt', () => {
const spot = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase)
const fixedLt = 10n ** 18n

const balances = calculator.computeFixedAllocation(spot, TOKEN_A, fixedLt)

expect(balances.reserveLt).toBe(fixedLt)
expect(balances.reserveGt).toBe(15n * 10n ** 17n)

const price = PeggedPrice.fromReserves({
linearWidth: LINEAR_WIDTH,
reserveA: {
address: TOKEN_A,
decimals: 18,
initialReserve: balances.reserveLt,
currentReserve: balances.reserveLt,
},
reserveB: {
address: TOKEN_B,
decimals: 18,
initialReserve: balances.reserveGt,
currentReserve: balances.reserveGt,
},
})

expect(price.toHuman(TOKEN_B)).toBe('1.5')
})

it('computeFixedAllocation derives initial Lt from fixed Gt (6 / 18 decimals)', () => {
const calc = PeggedSwapCalculator.new({
tokenA: { address: TOKEN_A, decimals: 6 },
tokenB: { address: TOKEN_B, decimals: 18 },
})

const spot = PeggedPrice.fromHuman('2', {
quoteToken: { address: TOKEN_B, decimals: 18 },
baseToken: { address: TOKEN_A, decimals: 6 },
})

const balances = calc.computeFixedAllocation(spot, TOKEN_B, 2n * 10n ** 18n)

expect(balances.reserveGt).toBe(2n * 10n ** 18n)
expect(balances.reserveLt).toBe(1n * 10n ** 6n)

const price = PeggedPrice.fromReserves({
linearWidth: LINEAR_WIDTH,
reserveA: {
address: TOKEN_A,
decimals: 6,
initialReserve: balances.reserveLt,
currentReserve: balances.reserveLt,
},
reserveB: {
address: TOKEN_B,
decimals: 18,
initialReserve: balances.reserveGt,
currentReserve: balances.reserveGt,
},
})

expect(price.toHuman(TOKEN_B)).toBe('2')
})

describe('stablecoin pairs', () => {
it('USDC/USDT: 0.998 USDT per 1 USDC with 1M USDC fixed', () => {
const calculator = PeggedSwapCalculator.new({
tokenA: { address: USDC, decimals: 6 },
tokenB: { address: USDT, decimals: 6 },
})

const spot = PeggedPrice.fromHuman('0.998', {
quoteToken: { address: USDT, decimals: 6 },
baseToken: { address: USDC, decimals: 6 },
})

const fixedUsdc = 1_000_000n * 10n ** 6n
const balances = calculator.computeFixedAllocation(spot, USDC, fixedUsdc)

expect(balances.reserveLt).toBe(fixedUsdc)
expect(balances.reserveGt).toBe(998_000n * 10n ** 6n)

const price = PeggedPrice.fromReserves({
linearWidth: LINEAR_WIDTH,
reserveA: {
address: USDC,
decimals: 6,
initialReserve: balances.reserveLt,
currentReserve: balances.reserveLt,
},
reserveB: {
address: USDT,
decimals: 6,
initialReserve: balances.reserveGt,
currentReserve: balances.reserveGt,
},
})

expect(price.toHuman(USDT)).toBe('0.998')
})

it('DAI/USDC: 1.002 USDC per 1 DAI with 1M DAI fixed', () => {
const calculator = PeggedSwapCalculator.new({
tokenA: { address: DAI, decimals: 18 },
tokenB: { address: USDC, decimals: 6 },
})

const spot = PeggedPrice.fromHuman('1.002', {
quoteToken: { address: USDC, decimals: 6 },
baseToken: { address: DAI, decimals: 18 },
})

const fixedDai = 1_000_000n * 10n ** 18n
const balances = calculator.computeFixedAllocation(spot, DAI, fixedDai)

expect(balances.reserveLt).toBe(fixedDai)
expect(balances.reserveGt).toBe(1_002_000n * 10n ** 6n)

const price = PeggedPrice.fromReserves({
linearWidth: LINEAR_WIDTH,
reserveA: {
address: DAI,
decimals: 18,
initialReserve: balances.reserveLt,
currentReserve: balances.reserveLt,
},
reserveB: {
address: USDC,
decimals: 6,
initialReserve: balances.reserveGt,
currentReserve: balances.reserveGt,
},
})

expect(price.toHuman(USDC)).toBe('1.002')
})
})
})
Loading