Skip to content
Open
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
4 changes: 3 additions & 1 deletion frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"@/ts-sdk": ["../ts-sdk/src/index.ts"],
"@/ts-sdk/*": ["../ts-sdk/src/*"]
}
},
"include": [
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
"check:bench": "pnpm run check:bench:manifest && pnpm run check:bench:phoenix",
"check:bench:manifest": "cd cu-bench/manifest && cargo check --tests",
"check:bench:phoenix": "cd cu-bench/phoenix && cargo check --tests",
"lint": "pnpm run lint:biome && pnpm run lint:rust",
"lint:fix": "pnpm run lint:biome:fix && pnpm run lint:rust:fix",
"lint:biome": "biome check",
"lint:biome:fix": "biome check --write",
"lint": "pnpm run lint:ts && pnpm run lint:rust",
"lint:fix": "pnpm run lint:ts:fix && pnpm run lint:rust:fix",
"lint:ts": "biome check",
"lint:ts:fix": "biome check --write",
"lint:rust": "cargo +nightly clippy --workspace --all-targets -- -D warnings",
"lint:rust:fix": "cargo +nightly clippy --fix --workspace --all-targets --allow-dirty --allow-staged -- -D warnings",
"lint:generated": "biome check --write codama-idl-gen ts-sdk/src/generated",
"format": "pnpm run format:biome && pnpm run format:rust",
"format:biome": "biome format --write",
"format": "pnpm run format:ts && pnpm run format:rust",
"format:ts": "biome format --write",
"format:rust": "cargo +nightly fmt --all",
"test": "pnpm run test:ts && pnpm run test:rust",
"test:ts": "turbo run test",
Expand Down
3 changes: 2 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ catalog:
"@jest/globals": ^29.7.0
"@solana/kit": ^6.1.0
"@types/node": ^24
"decimal.js": ^10.6.0
"jest": ^29.7.0
"ts-jest": ^29.4.6
"typescript": ^5
"typescript": ^5.9

5 changes: 3 additions & 2 deletions ts-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
},
"typings": "dist/common/index.d.ts",
"dependencies": {
"@solana/program-client-core": "^6.5.0",
"@solana/kit": "catalog:",
"@solana/rpc-transport-http": "^6.5.0"
"@solana/program-client-core": "^6.5.0",
"@solana/rpc-transport-http": "^6.5.0",
"decimal.js": "catalog:"
},
"devDependencies": {
"@jest/globals": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion ts-sdk/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
getMarketHeaderDecoder,
getMarketSeatDecoder,
getSectorDecoder,
} from "@/generated";
} from "@/ts-sdk/generated";

export const LOCALNET_URL = "http://localhost:8899";
export const NIL = 0xffffffff;
Expand Down
8 changes: 4 additions & 4 deletions ts-sdk/src/dropset-interface/market-view-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import type {
FixedSizeDecoder,
ReadonlyUint8Array,
} from "@solana/kit";
import { NIL, SECTOR_SIZE } from "@/const";
import { NIL, SECTOR_SIZE } from "@/ts-sdk/const";
import type {
MarketAccount,
MarketHeader,
MarketSeat,
Order,
Sector,
} from "@/generated";
} from "@/ts-sdk/generated";
import {
getMarketSeatDecoder,
getOrderDecoder,
getSectorDecoder,
} from "@/generated";
import type { Flatten, SectorIndex } from "@/types";
} from "@/ts-sdk/generated";
import type { Flatten, SectorIndex } from "@/ts-sdk/types";

export type MarketViewAll = {
header: MarketHeader;
Expand Down
2 changes: 2 additions & 0 deletions ts-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./dropset-interface";
export * from "./generated";
export * from "./price";
export * from "./rust-types";
export * from "./types";
export * from "./utils";
105 changes: 105 additions & 0 deletions ts-sdk/src/price/client-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Decimal } from "decimal.js";
import {
ensureU8,
ensureU64,
type U8,
type U32,
type U64,
} from "../rust-types";
import { decodedPriceToDecimal, decodePrice } from "./decoded-price";
import { PriceError } from "./error";
import {
BIAS,
normalizePriceMantissa,
UNBIASED_MAX,
UNBIASED_MIN,
} from "./lib";

/** Multiply `value` by `10^pow`. Port of `decimal_pow10` in `price/src/client_helpers.rs`. */
export function decimalPow10(value: Decimal, pow: number): Decimal {
if (pow === 0) return value;
return value.times(new Decimal(10).pow(pow));
}

/** Port of `try_to_biased_exponent` in `price/src/client_helpers.rs`. */
export function toBiasedExponent(unbiased: number): U8 {
if (unbiased < UNBIASED_MIN || unbiased > UNBIASED_MAX) {
throw new Error(PriceError.InvalidBiasedExponent);
}
return ensureU8(unbiased + BIAS);
}

/** Port of `atoms_to_ui_amount` in `price/src/client_helpers.rs`. */
export function atomsToUiAmount(
atomsAmount: bigint,
mintDecimals: number | bigint,
): Decimal {
const dec = ensureU8(mintDecimals);
return decimalPow10(new Decimal(atomsAmount.toString()), -dec);
}

/**
* Convert a UI price (human-readable quote/base) to an atoms-denominated price,
* accounting for differing base/quote decimals.
*
* `atomsPrice = uiPrice * 10^(quoteDecimals - baseDecimals)`
*
* Port of `ui_price_to_atoms_price` in `price/src/client_helpers.rs`.
*/
export function uiPriceToAtomsPrice(
uiPrice: Decimal,
baseDecimals: number | bigint,
quoteDecimals: number | bigint,
): Decimal {
const base = ensureU8(baseDecimals);
const quote = ensureU8(quoteDecimals);
return decimalPow10(uiPrice, quote - base);
}

/** Port of `try_encoded_u32_to_decoded_decimal` in `price/src/client_helpers.rs`. */
export function encodedU32ToDecimal(encodedU32: number | bigint): Decimal {
return decodedPriceToDecimal(decodePrice(encodedU32));
}

/** Port of `get_sig_figs` in `price/src/client_helpers.rs`. */
function getSigFigs(value: bigint): { scalar: bigint; pow: number } {
if (value === 0n) throw new Error(PriceError.AmountCannotBeZero);
let x = value;
let pow = 0;
while (x % 10n === 0n) {
x /= 10n;
pow += 1;
}
return { scalar: x, pow };
}

/**
* Convert a decimal price and base-atoms order size into `OrderInfoArgs`-equivalent values.
*
* Port of `to_order_info_args` in `price/src/client_helpers.rs`.
*/
export function toOrderInfoArgs(
price: Decimal,
orderSizeBaseAtoms: bigint,
): {
priceMantissa: U32;
baseScalar: U64;
baseExponentBiased: U8;
quoteExponentBiased: U8;
} {
const { mantissa, scale: priceExponent } = normalizePriceMantissa(price);

if (orderSizeBaseAtoms === 0n) throw new Error(PriceError.AmountCannotBeZero);
ensureU64(orderSizeBaseAtoms);

const { scalar: baseScalar, pow: baseExponentUnbiased } =
getSigFigs(orderSizeBaseAtoms);
const quoteExponentUnbiased = priceExponent + baseExponentUnbiased;

return {
priceMantissa: mantissa.value,
baseScalar: ensureU64(baseScalar),
baseExponentBiased: toBiasedExponent(baseExponentUnbiased),
quoteExponentBiased: toBiasedExponent(quoteExponentUnbiased),
};
}
50 changes: 50 additions & 0 deletions ts-sdk/src/price/decoded-price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Decimal } from "decimal.js";
import { ensureU8, ensureU32, type U8 } from "../rust-types";
import { decimalPow10 } from "./client-helpers";
import { ENCODED_PRICE_INFINITY, ENCODED_PRICE_ZERO } from "./encoded-price";
import { PriceError } from "./error";
import { BIAS, PRICE_MANTISSA_BITS, PRICE_MANTISSA_MASK } from "./lib";
Comment on lines +1 to +6
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a circular module dependency between decoded-price.ts and client-helpers.ts (decoded-price imports decimalPow10 from client-helpers, while client-helpers imports decodePrice/decodedPriceToDecimal from decoded-price). With Jest/ts-jest (CommonJS) this pattern can lead to partially-initialized exports at runtime (e.g. decodePrice being undefined inside client-helpers), depending on evaluation order. Consider breaking the cycle by moving decimalPow10 into a small standalone module (e.g. pow10.ts) or by inlining the power-of-10 logic in decodedPriceToDecimal.

Copilot uses AI. Check for mistakes.
import {
type ValidatedPriceMantissa,
validatePriceMantissa,
} from "./validated-mantissa";

/**
* An enum representing a decoded `EncodedPrice`.
*
* Port of `DecodedPrice` in `price/src/decoded_price.rs`.
*/
export type DecodedPrice =
| { kind: "zero" }
| { kind: "infinity" }
| {
kind: "value";
biasedExponent: U8;
mantissa: ValidatedPriceMantissa;
};

/** Port of `DecodedPrice::try_from(EncodedPrice)` in `price/src/decoded_price.rs`. */
export function decodePrice(encoded: number | bigint): DecodedPrice {
const v = ensureU32(encoded);
if (v === ENCODED_PRICE_ZERO) return { kind: "zero" };
if (v === ENCODED_PRICE_INFINITY) return { kind: "infinity" };

const biasedExponent = ensureU8(v >>> PRICE_MANTISSA_BITS);
const mantissa = validatePriceMantissa(v & PRICE_MANTISSA_MASK);
return { kind: "value", biasedExponent, mantissa };
}

/** Port of `Decimal::try_from(DecodedPrice)` in `price/src/decoded_price.rs`. */
export function decodedPriceToDecimal(decoded: DecodedPrice): Decimal {
switch (decoded.kind) {
case "zero":
return new Decimal(0);
case "infinity":
throw new Error(PriceError.InfinityIsNotADecimal);
case "value":
return decimalPow10(
new Decimal(decoded.mantissa.value),
decoded.biasedExponent - BIAS,
);
}
}
37 changes: 37 additions & 0 deletions ts-sdk/src/price/encoded-price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ensureU8, type U32, U32_MAX } from "../rust-types";
import { PriceError } from "./error";
import { PRICE_EXPONENT_MAX, PRICE_MANTISSA_BITS } from "./lib";
import type { ValidatedPriceMantissa } from "./validated-mantissa";

/**
* An encoded price packed into a u32: `[exponent_bits | mantissa_bits]`.
*
* Port of `EncodedPrice` in `price/src/encoded_price.rs`.
*/
export type EncodedPrice = U32;

const ENCODED_PRICE_INFINITY = U32_MAX as EncodedPrice;
const ENCODED_PRICE_ZERO = 0 as EncodedPrice;

export { ENCODED_PRICE_INFINITY, ENCODED_PRICE_ZERO };

/** Port of `EncodedPrice::new` in `price/src/encoded_price.rs`. */
export function encodePrice(
mantissa: ValidatedPriceMantissa,
biasedExponent: number | bigint,
): EncodedPrice {
const exp = ensureU8(biasedExponent);
if (exp > PRICE_EXPONENT_MAX) {
throw new Error(PriceError.InvalidBiasedExponent);
}
return (((exp << PRICE_MANTISSA_BITS) | mantissa.value) >>>
0) as EncodedPrice;
}

export function isEncodedPriceInfinity(encoded: EncodedPrice): boolean {
return encoded === ENCODED_PRICE_INFINITY;
}

export function isEncodedPriceZero(encoded: EncodedPrice): boolean {
return encoded === ENCODED_PRICE_ZERO;
}
9 changes: 9 additions & 0 deletions ts-sdk/src/price/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Port of `OrderInfoError` in `price/src/error.rs`. */
export enum PriceError {
ExponentUnderflow = "ExponentUnderflow",
ArithmeticOverflow = "ArithmeticOverflow",
InvalidPriceMantissa = "InvalidPriceMantissa",
InvalidBiasedExponent = "InvalidBiasedExponent",
InfinityIsNotADecimal = "InfinityIsNotADecimal",
AmountCannotBeZero = "AmountCannotBeZero",
}
7 changes: 7 additions & 0 deletions ts-sdk/src/price/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./client-helpers";
export * from "./decoded-price";
export * from "./encoded-price";
export * from "./error";
export * from "./lib";
export * from "./utils";
export * from "./validated-mantissa";
Loading