Skip to content

Commit 0b2c9ad

Browse files
committed
sdk: add reference price helpers
1 parent 4c9ef55 commit 0b2c9ad

7 files changed

Lines changed: 392 additions & 0 deletions

File tree

docs/helpers-and-builders.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,54 @@ const listings = await sdk.listings.getListings(params);
127127
Relevant types:
128128
`CsfloatCategoryPreset`, `CsfloatHomepageFeedPreset`, `CsfloatPriceRangeParams`, `CsfloatFadeRangeParams`, `CsfloatBlueRangeParams`, `CsfloatReferenceQuantityParams`, `CsfloatCollectionFilterParams`, `CsfloatRarityFilterParams`, `CsfloatPaintSeedFilterParams`, `CsfloatMusicKitFilterParams`, `CsfloatKeychainPatternRangeParams`
129129

130+
## Reference Price Helpers
131+
132+
Listings, watchlist entries, stall listings, and some inventory items can include a `reference` object that powers the marketplace-style price widget:
133+
134+
- base price
135+
- item factor
136+
- final/predicted price
137+
- global listing count
138+
- deal percentage versus the current listing price
139+
140+
The SDK already exposes the raw field as `listing.reference` / `inventoryItem.reference`. These helpers make it easier to work with it directly.
141+
142+
| Export | Use It For |
143+
|---|---|
144+
| `getReferencePrice(target)` | extract the raw `reference` object from a listing, inventory item, or reference payload |
145+
| `getReferenceItemFactorAmount(target)` | compute the absolute item-factor amount (`predicted_price - base_price`) |
146+
| `getReferenceDiscountPercent(target, listingPrice?)` | compute the positive discount percent when a listing is below the predicted price |
147+
| `getReferencePremiumPercent(target, listingPrice?)` | compute the positive premium percent when a listing is above the predicted price |
148+
| `buildReferenceInsight(target, listingPrice?)` | build one summary object with base/final price, item factor amount, quantity, and discount/premium info |
149+
150+
Notes:
151+
152+
- `finalPrice` in `buildReferenceInsight()` maps to the API field `predicted_price`
153+
- `globalListings` maps to the API field `quantity`
154+
- all values stay in the same integer price units used by `listing.price` and the API payloads
155+
156+
Example:
157+
158+
```ts
159+
import {
160+
buildReferenceInsight,
161+
getReferenceDiscountPercent,
162+
} from "csfloat-node-sdk";
163+
164+
const [listing] = (await sdk.listings.getListings()).data;
165+
166+
const insight = buildReferenceInsight(listing);
167+
168+
console.log(insight?.basePrice);
169+
console.log(insight?.itemFactorAmount);
170+
console.log(insight?.finalPrice);
171+
console.log(insight?.globalListings);
172+
console.log(getReferenceDiscountPercent(listing));
173+
```
174+
175+
Relevant types:
176+
`CsfloatReferencePrice`, `CsfloatReferenceInsight`, `CsfloatReferenceTarget`
177+
130178
## Loadout Helpers
131179

132180
### Constants

docs/resource-reference.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ Write payload guide:
105105
Key types:
106106
`CsfloatWatchlistParams`, `CsfloatListingsResponse`, `CsfloatNotificationsParams`, `CsfloatNotificationsResponse`
107107

108+
Watchlist note:
109+
returned `CsfloatListing` rows can include `reference`, which carries the base/predicted price widget data used by the marketplace UI.
110+
108111
### Transactions, Payments, And Status
109112

110113
| Method | Returns | Use It For | Notes |
@@ -150,6 +153,9 @@ Write payload guide:
150153
|---|---|---|---|
151154
| `getInventory()` | `Promise<CsfloatInventoryResponse>` | authenticated inventory lookup | simple read-only resource |
152155

156+
Inventory note:
157+
some `CsfloatInventoryItem` rows can include `reference` with the same base/predicted price metadata seen on listing responses.
158+
153159
## `sdk.users`
154160

155161
| Method | Returns | Use It For | Notes |
@@ -166,6 +172,9 @@ Write payload guide:
166172
Key types:
167173
`CsfloatStallParams`, `CsfloatListingsResponse`, `CsfloatListing`
168174

175+
Stall note:
176+
stall listing rows use the same `CsfloatListing` shape as public search results and can also include `reference`.
177+
169178
## `sdk.listings`
170179

171180
### Public Reads, Watchlist Mutation, And Purchases
@@ -188,6 +197,9 @@ Key types:
188197
Key types:
189198
`CsfloatListParams`, `CsfloatListingsResponse`, `CsfloatListing`, `CsfloatPriceListEntry`, `CsfloatBid`, `BuyNowRequest`, `PlaceBidRequest`
190199

200+
Listing note:
201+
`CsfloatListing.reference` can carry the same data shown by the marketplace reference widget: `base_price`, `predicted_price`, `quantity`, and the float-factor multiplier. Use the reference helpers from [Helpers, Builders, And Constants](./helpers-and-builders.md#reference-price-helpers) if you want derived values such as item-factor amount or deal percentage.
202+
191203
### Listing Writes
192204

193205
| Method | Returns | Use It For | Notes |

src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ export { CsfloatHttpClient } from "./client.js";
22
export { CsfloatSdk } from "./sdk.js";
33
export { CsfloatSdkError, isCsfloatSdkError } from "./errors.js";
44
export { WorkflowResource } from "./resources/workflows.js";
5+
export {
6+
buildReferenceInsight,
7+
getReferenceDiscountPercent,
8+
getReferenceItemFactorAmount,
9+
getReferencePremiumPercent,
10+
getReferencePrice,
11+
} from "./reference.js";
512
export {
613
buildExpressionBuyOrderRequest,
714
buildSingleSkinBuyOrderExpression,
@@ -84,6 +91,11 @@ export type {
8491
CsfloatResponseMetadata,
8592
} from "./client.js";
8693
export type { CsfloatErrorKind, CsfloatSdkErrorOptions } from "./errors.js";
94+
export type {
95+
CsfloatReferenceCarrier,
96+
CsfloatReferenceInsight,
97+
CsfloatReferenceTarget,
98+
} from "./reference.js";
8799
export type {
88100
CsfloatBuyOrderComparableField,
89101
CsfloatSingleSkinBuyOrderExpressionOptions,

src/reference.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import type { CsfloatInventoryItem, CsfloatListing, CsfloatReferencePrice } from "./types.js";
2+
3+
export type CsfloatReferenceCarrier = Pick<CsfloatListing, "price" | "reference"> | Pick<CsfloatInventoryItem, "reference">;
4+
5+
export type CsfloatReferenceTarget =
6+
| CsfloatReferencePrice
7+
| CsfloatReferenceCarrier
8+
| null
9+
| undefined;
10+
11+
export interface CsfloatReferenceInsight {
12+
basePrice?: number;
13+
itemFactorMultiplier?: number;
14+
itemFactorAmount?: number;
15+
finalPrice?: number;
16+
globalListings?: number;
17+
lastUpdated?: string;
18+
listingPrice?: number;
19+
priceDelta?: number;
20+
discountPercent?: number;
21+
premiumPercent?: number;
22+
}
23+
24+
function isReferencePrice(target: CsfloatReferenceTarget): target is CsfloatReferencePrice {
25+
return Boolean(
26+
target &&
27+
typeof target === "object" &&
28+
("base_price" in target ||
29+
"predicted_price" in target ||
30+
"float_factor" in target ||
31+
"quantity" in target ||
32+
"last_updated" in target),
33+
);
34+
}
35+
36+
function extractReference(target: CsfloatReferenceTarget): CsfloatReferencePrice | undefined {
37+
if (!target) {
38+
return undefined;
39+
}
40+
41+
if (isReferencePrice(target)) {
42+
return target;
43+
}
44+
45+
return target.reference ?? undefined;
46+
}
47+
48+
function extractListingPrice(
49+
target: CsfloatReferenceTarget,
50+
overridePrice?: number | null,
51+
): number | undefined {
52+
if (overridePrice !== undefined && overridePrice !== null) {
53+
return overridePrice;
54+
}
55+
56+
if (!target || isReferencePrice(target) || !("price" in target)) {
57+
return undefined;
58+
}
59+
60+
return typeof target.price === "number" ? target.price : undefined;
61+
}
62+
63+
function computePriceDelta(
64+
listingPrice: number | undefined,
65+
finalPrice: number | undefined,
66+
): number | undefined {
67+
if (listingPrice === undefined || finalPrice === undefined) {
68+
return undefined;
69+
}
70+
71+
return listingPrice - finalPrice;
72+
}
73+
74+
function computeRelativePercent(
75+
numerator: number | undefined,
76+
denominator: number | undefined,
77+
): number | undefined {
78+
if (numerator === undefined || denominator === undefined || denominator <= 0) {
79+
return undefined;
80+
}
81+
82+
return (numerator / denominator) * 100;
83+
}
84+
85+
export function getReferencePrice(
86+
target: CsfloatReferenceTarget,
87+
): CsfloatReferencePrice | undefined {
88+
return extractReference(target);
89+
}
90+
91+
export function getReferenceItemFactorAmount(
92+
target: CsfloatReferenceTarget,
93+
): number | undefined {
94+
const reference = extractReference(target);
95+
if (!reference || reference.base_price === undefined || reference.predicted_price === undefined) {
96+
return undefined;
97+
}
98+
99+
return reference.predicted_price - reference.base_price;
100+
}
101+
102+
export function getReferenceDiscountPercent(
103+
target: CsfloatReferenceTarget,
104+
listingPrice?: number | null,
105+
): number | undefined {
106+
const reference = extractReference(target);
107+
const price = extractListingPrice(target, listingPrice);
108+
const finalPrice = reference?.predicted_price;
109+
110+
if (price === undefined || finalPrice === undefined || finalPrice <= 0 || price >= finalPrice) {
111+
return undefined;
112+
}
113+
114+
return computeRelativePercent(finalPrice - price, finalPrice);
115+
}
116+
117+
export function getReferencePremiumPercent(
118+
target: CsfloatReferenceTarget,
119+
listingPrice?: number | null,
120+
): number | undefined {
121+
const reference = extractReference(target);
122+
const price = extractListingPrice(target, listingPrice);
123+
const finalPrice = reference?.predicted_price;
124+
125+
if (price === undefined || finalPrice === undefined || finalPrice <= 0 || price <= finalPrice) {
126+
return undefined;
127+
}
128+
129+
return computeRelativePercent(price - finalPrice, finalPrice);
130+
}
131+
132+
export function buildReferenceInsight(
133+
target: CsfloatReferenceTarget,
134+
listingPrice?: number | null,
135+
): CsfloatReferenceInsight | undefined {
136+
const reference = extractReference(target);
137+
if (!reference) {
138+
return undefined;
139+
}
140+
141+
const price = extractListingPrice(target, listingPrice);
142+
const finalPrice = reference.predicted_price;
143+
const insight: CsfloatReferenceInsight = {};
144+
145+
if (reference.base_price !== undefined) {
146+
insight.basePrice = reference.base_price;
147+
}
148+
149+
if (reference.float_factor !== undefined) {
150+
insight.itemFactorMultiplier = reference.float_factor;
151+
}
152+
153+
const itemFactorAmount = getReferenceItemFactorAmount(reference);
154+
if (itemFactorAmount !== undefined) {
155+
insight.itemFactorAmount = itemFactorAmount;
156+
}
157+
158+
if (finalPrice !== undefined) {
159+
insight.finalPrice = finalPrice;
160+
}
161+
162+
if (reference.quantity !== undefined) {
163+
insight.globalListings = reference.quantity;
164+
}
165+
166+
if (reference.last_updated !== undefined) {
167+
insight.lastUpdated = reference.last_updated;
168+
}
169+
170+
if (price !== undefined) {
171+
insight.listingPrice = price;
172+
}
173+
174+
const priceDelta = computePriceDelta(price, finalPrice);
175+
if (priceDelta !== undefined) {
176+
insight.priceDelta = priceDelta;
177+
}
178+
179+
const discountPercent = getReferenceDiscountPercent(reference, price);
180+
if (discountPercent !== undefined) {
181+
insight.discountPercent = discountPercent;
182+
}
183+
184+
const premiumPercent = getReferencePremiumPercent(reference, price);
185+
if (premiumPercent !== undefined) {
186+
insight.premiumPercent = premiumPercent;
187+
}
188+
189+
return insight;
190+
}

test/reference.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import {
4+
buildReferenceInsight,
5+
getReferenceDiscountPercent,
6+
getReferenceItemFactorAmount,
7+
getReferencePremiumPercent,
8+
getReferencePrice,
9+
} from "../src/reference.js";
10+
import type { CsfloatListing, CsfloatReferencePrice } from "../src/types.js";
11+
12+
const reference: CsfloatReferencePrice = {
13+
base_price: 401_898,
14+
float_factor: 1.19481,
15+
predicted_price: 480_191,
16+
quantity: 57,
17+
last_updated: "2026-03-10T20:36:22.015139Z",
18+
};
19+
20+
const discountedListing: Pick<CsfloatListing, "price" | "reference"> = {
21+
price: 407_747,
22+
reference,
23+
};
24+
25+
describe("reference helpers", () => {
26+
it("extracts the raw reference price object", () => {
27+
expect(getReferencePrice(reference)).toEqual(reference);
28+
expect(getReferencePrice(discountedListing)).toEqual(reference);
29+
});
30+
31+
it("builds reference insight values for a discounted listing", () => {
32+
expect(buildReferenceInsight(discountedListing)).toEqual({
33+
basePrice: 401_898,
34+
itemFactorMultiplier: 1.19481,
35+
itemFactorAmount: 78_293,
36+
finalPrice: 480_191,
37+
globalListings: 57,
38+
lastUpdated: "2026-03-10T20:36:22.015139Z",
39+
listingPrice: 407_747,
40+
priceDelta: -72_444,
41+
discountPercent: ((480_191 - 407_747) / 480_191) * 100,
42+
premiumPercent: undefined,
43+
});
44+
});
45+
46+
it("computes the marketplace-style discount percentage from reference data", () => {
47+
expect(getReferenceDiscountPercent(discountedListing)).toBeCloseTo(15.0865, 3);
48+
expect(getReferenceItemFactorAmount(discountedListing)).toBe(78_293);
49+
expect(getReferencePremiumPercent(discountedListing)).toBeUndefined();
50+
});
51+
52+
it("computes premium percentages for listings above the predicted price", () => {
53+
const premiumListing: Pick<CsfloatListing, "price" | "reference"> = {
54+
price: 500_000,
55+
reference,
56+
};
57+
58+
expect(getReferenceDiscountPercent(premiumListing)).toBeUndefined();
59+
expect(getReferencePremiumPercent(premiumListing)).toBeCloseTo(
60+
((500_000 - 480_191) / 480_191) * 100,
61+
3,
62+
);
63+
});
64+
65+
it("returns undefined when no reference data is available", () => {
66+
expect(buildReferenceInsight(undefined)).toBeUndefined();
67+
expect(getReferencePrice(undefined)).toBeUndefined();
68+
expect(getReferenceDiscountPercent(undefined)).toBeUndefined();
69+
});
70+
});

0 commit comments

Comments
 (0)