@sundaeswap/math 0.3.0 → 0.5.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.
- package/dist/cjs/PoolMath/ConstantSumPool.js +106 -1
- package/dist/cjs/PoolMath/ConstantSumPool.js.map +1 -1
- package/dist/cjs/PoolMath/V4StableswapPool.js +529 -0
- package/dist/cjs/PoolMath/V4StableswapPool.js.map +1 -0
- package/dist/cjs/PoolMath/index.js +3 -1
- package/dist/cjs/PoolMath/index.js.map +1 -1
- package/dist/esm/PoolMath/ConstantSumPool.js +81 -0
- package/dist/esm/PoolMath/ConstantSumPool.js.map +1 -1
- package/dist/esm/PoolMath/V4StableswapPool.js +495 -0
- package/dist/esm/PoolMath/V4StableswapPool.js.map +1 -0
- package/dist/esm/PoolMath/index.js +1 -0
- package/dist/esm/PoolMath/index.js.map +1 -1
- package/dist/types/PoolMath/ConstantSumPool.d.ts +43 -0
- package/dist/types/PoolMath/ConstantSumPool.d.ts.map +1 -1
- package/dist/types/PoolMath/V4StableswapPool.d.ts +221 -0
- package/dist/types/PoolMath/V4StableswapPool.d.ts.map +1 -0
- package/dist/types/PoolMath/index.d.ts +1 -0
- package/dist/types/PoolMath/index.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/PoolMath/ConstantSumPool.ts +131 -0
- package/src/PoolMath/V4StableswapPool.ts +638 -0
- package/src/PoolMath/__tests__/ConstantSumPool.test.ts +167 -6
- package/src/PoolMath/__tests__/V4StableswapPool.test.ts +688 -0
- package/src/PoolMath/index.ts +1 -0
|
@@ -369,3 +369,134 @@ export const calculateDepositN = (
|
|
|
369
369
|
shareAfterDeposit: SharedPoolMath.getShare(generatedLp, nextTotalLp),
|
|
370
370
|
};
|
|
371
371
|
};
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Outcome of {@link calculateZap}. Every array is aligned to `offered`.
|
|
375
|
+
*/
|
|
376
|
+
export type TZapOutcome = {
|
|
377
|
+
/** Index of the offered asset swapped away; `undefined` when the basket already matches the reserves. */
|
|
378
|
+
swapIndex: number | undefined;
|
|
379
|
+
/** Amount of `offered[swapIndex]` swapped away. */
|
|
380
|
+
swapInput: bigint;
|
|
381
|
+
/** Amount of the other asset the swap yields. */
|
|
382
|
+
swapOutput: bigint;
|
|
383
|
+
/** Reserves after the swap — what the deposit pins against. */
|
|
384
|
+
nextReserves: bigint[];
|
|
385
|
+
/** `offered` after the swap: what is actually deposited. */
|
|
386
|
+
depositBasket: bigint[];
|
|
387
|
+
/** Per-asset ceil-pinned deposit. */
|
|
388
|
+
deltas: bigint[];
|
|
389
|
+
/** `depositBasket − deltas`: surplus returned to the user. */
|
|
390
|
+
change: bigint[];
|
|
391
|
+
targetDeltaV: bigint;
|
|
392
|
+
generatedLp: bigint;
|
|
393
|
+
nextTotalLp: bigint;
|
|
394
|
+
shareAfterDeposit: Fraction;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* A zap on a 2-asset constant-sum pool: a non-proportional `offered` basket
|
|
399
|
+
* is rebalanced by swapping part of the over-weighted asset, then deposited
|
|
400
|
+
* under the target-pinned rule ({@link calculatePinnedDeposit}). The swap
|
|
401
|
+
* amount `dx` is the one that leaves the post-swap basket proportional to
|
|
402
|
+
* the post-swap reserves, so the pin consumes the whole basket up to
|
|
403
|
+
* rounding. With `k = (1−fee)·p_X/p_Y`,
|
|
404
|
+
*
|
|
405
|
+
* (a_X − dx)/(r_X + dx) = (a_Y + k·dx)/(r_Y − k·dx)
|
|
406
|
+
* ⇒ dx = (a_X·r_Y − a_Y·r_X) / (k·(r_X + a_X) + (r_Y + a_Y))
|
|
407
|
+
*
|
|
408
|
+
* (the dx² terms cancel). `dx` is floored and the swap leg follows
|
|
409
|
+
* {@link getSwapOutput}; whatever imbalance rounding leaves comes back as
|
|
410
|
+
* `change`.
|
|
411
|
+
*
|
|
412
|
+
* `fee` is the pool's fee for the swap direction — v4 splits bid and ask, so
|
|
413
|
+
* the caller picks. Two assets only: with more, which assets to swap is an
|
|
414
|
+
* allocation problem this formula does not address.
|
|
415
|
+
*/
|
|
416
|
+
export const calculateZap = (
|
|
417
|
+
offered: bigint[],
|
|
418
|
+
reserves: bigint[],
|
|
419
|
+
prices: bigint[],
|
|
420
|
+
totalLp: bigint,
|
|
421
|
+
fee: TFractionLike,
|
|
422
|
+
): TZapOutcome => {
|
|
423
|
+
if (offered.length !== 2 || reserves.length !== 2 || prices.length !== 2)
|
|
424
|
+
throw new Error("calculateZap supports exactly two assets");
|
|
425
|
+
if (prices.some((p) => p <= 0n)) throw new Error("Prices must be positive");
|
|
426
|
+
if (reserves.some((r) => r <= 0n))
|
|
427
|
+
throw new Error("Reserves must be positive");
|
|
428
|
+
if (offered.some((a) => a < 0n) || offered.every((a) => a === 0n))
|
|
429
|
+
throw new Error(
|
|
430
|
+
"Offered amounts must be non-negative, with at least one positive",
|
|
431
|
+
);
|
|
432
|
+
if (totalLp <= 0n) throw new Error("Not enough pool liquidity");
|
|
433
|
+
|
|
434
|
+
const feeFraction = Fraction.asFraction(fee);
|
|
435
|
+
if (feeFraction.lt(Fraction.ZERO) || feeFraction.gte(Fraction.ONE))
|
|
436
|
+
throw new Error("fee must be [0,1)");
|
|
437
|
+
const feeNum = BigInt(feeFraction.numerator);
|
|
438
|
+
const feeDen = BigInt(feeFraction.denominator);
|
|
439
|
+
|
|
440
|
+
// Over-weighted side: compare a_0/r_0 against a_1/r_1 without division.
|
|
441
|
+
const lhs = offered[0] * reserves[1];
|
|
442
|
+
const rhs = offered[1] * reserves[0];
|
|
443
|
+
const swapIndex = lhs > rhs ? 0 : lhs < rhs ? 1 : undefined;
|
|
444
|
+
|
|
445
|
+
let swapInput = 0n;
|
|
446
|
+
let swapOutput = 0n;
|
|
447
|
+
const nextReserves = [...reserves];
|
|
448
|
+
const depositBasket = [...offered];
|
|
449
|
+
|
|
450
|
+
if (swapIndex !== undefined) {
|
|
451
|
+
const x = swapIndex;
|
|
452
|
+
const y = 1 - swapIndex;
|
|
453
|
+
const [aX, aY] = [offered[x], offered[y]];
|
|
454
|
+
const [rX, rY] = [reserves[x], reserves[y]];
|
|
455
|
+
const [pX, pY] = [prices[x], prices[y]];
|
|
456
|
+
|
|
457
|
+
// dx with k = (feeDen−feeNum)·pX / (feeDen·pY), cleared of denominators.
|
|
458
|
+
const numerator = (aX * rY - aY * rX) * feeDen * pY;
|
|
459
|
+
const denominator =
|
|
460
|
+
(feeDen - feeNum) * pX * (rX + aX) + feeDen * pY * (rY + aY);
|
|
461
|
+
swapInput = numerator / denominator;
|
|
462
|
+
|
|
463
|
+
if (swapInput > 0n) {
|
|
464
|
+
const inputValue = swapInput * pX;
|
|
465
|
+
const feeValue = (inputValue * feeNum) / feeDen;
|
|
466
|
+
swapOutput = (inputValue - feeValue) / pY;
|
|
467
|
+
if (swapOutput >= rY)
|
|
468
|
+
throw new Error(
|
|
469
|
+
"Pool too shallow: the rebalancing swap would drain it",
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
nextReserves[x] = rX + swapInput;
|
|
473
|
+
nextReserves[y] = rY - swapOutput;
|
|
474
|
+
depositBasket[x] = aX - swapInput;
|
|
475
|
+
depositBasket[y] = aY + swapOutput;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (depositBasket.some((a) => a <= 0n))
|
|
480
|
+
throw new Error("Offered amount too small to zap into this pool");
|
|
481
|
+
|
|
482
|
+
const pinned = calculatePinnedDeposit(
|
|
483
|
+
depositBasket,
|
|
484
|
+
nextReserves,
|
|
485
|
+
prices,
|
|
486
|
+
totalLp,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
swapIndex,
|
|
491
|
+
swapInput,
|
|
492
|
+
swapOutput,
|
|
493
|
+
nextReserves,
|
|
494
|
+
depositBasket,
|
|
495
|
+
deltas: pinned.deltas,
|
|
496
|
+
change: depositBasket.map((a, i) => a - pinned.deltas[i]),
|
|
497
|
+
targetDeltaV: pinned.targetDeltaV,
|
|
498
|
+
generatedLp: pinned.generatedLp,
|
|
499
|
+
nextTotalLp: pinned.nextTotalLp,
|
|
500
|
+
shareAfterDeposit: pinned.shareAfterDeposit,
|
|
501
|
+
};
|
|
502
|
+
};
|