@assetpay/steam-mobile 1.1.0 → 1.2.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/index.mjs CHANGED
@@ -358,6 +358,12 @@ var ConfirmationError = class extends SteamError {
358
358
  this.name = "ConfirmationError";
359
359
  }
360
360
  };
361
+ var ConfirmationOutcomeUnknownError = class extends ConfirmationError {
362
+ constructor(message = "Confirmation response unreadable; the outcome is unknown") {
363
+ super(message);
364
+ this.name = "ConfirmationOutcomeUnknownError";
365
+ }
366
+ };
361
367
  var FamilyViewError = class extends SteamError {
362
368
  constructor(message = "Family View Restricted") {
363
369
  super(message);
@@ -371,9 +377,59 @@ var PrivateInventoryError = class extends SteamError {
371
377
  }
372
378
  };
373
379
  var MarketConfirmationLimitError = class extends SteamError {
380
+ reason;
374
381
  constructor(message = "Too many listings pending confirmation", options) {
375
382
  super(message, options);
376
383
  this.name = "MarketConfirmationLimitError";
384
+ this.reason = options?.reason ?? "unknown";
385
+ }
386
+ };
387
+ var MarketItemUnavailableError = class extends SteamError {
388
+ constructor(message, options) {
389
+ super(message, options);
390
+ this.name = "MarketItemUnavailableError";
391
+ }
392
+ };
393
+ var MarketIneligibleError = class extends SteamError {
394
+ constructor(message = "Steam redirected to the market eligibility check — this account cannot use the market") {
395
+ super(message);
396
+ this.name = "MarketIneligibleError";
397
+ }
398
+ };
399
+ var CurrencyMismatchError = class extends SteamError {
400
+ expected;
401
+ actual;
402
+ constructor(expected, actual, context) {
403
+ super(`Currency mismatch in ${context}: expected ${expected}, got ${actual ?? "unknown"}`);
404
+ this.name = "CurrencyMismatchError";
405
+ this.expected = expected;
406
+ this.actual = actual;
407
+ }
408
+ };
409
+ var WalletCurrencyUnknownError = class extends SteamError {
410
+ constructor(context) {
411
+ super(`Wallet currency unknown for ${context} — seed walletCurrency or call getWalletDetails() first`);
412
+ this.name = "WalletCurrencyUnknownError";
413
+ }
414
+ };
415
+ var BuyOrderError = class extends SteamError {
416
+ stage;
417
+ code;
418
+ mayExist;
419
+ orderExists;
420
+ confirmationId;
421
+ constructor(message, options) {
422
+ super(message, {
423
+ ...options.code !== void 0 ? { eresult: options.code } : {},
424
+ body: options.body,
425
+ cause: options.cause
426
+ });
427
+ this.name = "BuyOrderError";
428
+ this.stage = options.stage;
429
+ this.code = options.code;
430
+ this.orderExists = options.orderExists ?? false;
431
+ this.mayExist = options.mayExist || this.orderExists;
432
+ this.confirmationId = options.confirmationId;
377
433
  }
378
434
  };
379
435
  var WalletBalanceLimitError = class extends SteamError {
@@ -909,6 +965,12 @@ const IMPIT_BROWSER = {
909
965
  }
910
966
  };
911
967
  const BROWSER_ACCEPT$1 = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
968
+ function requestControl(control) {
969
+ const out = {};
970
+ if (control?.signal) out.signal = control.signal;
971
+ if (control?.timeoutMs !== void 0) out.timeoutMs = control.timeoutMs;
972
+ return out;
973
+ }
912
974
  function clean(input) {
913
975
  if (!input) return void 0;
914
976
  const out = {};
@@ -952,7 +1014,8 @@ var HttpClient = class {
952
1014
  const res = await this.perform(method, url, opts);
953
1015
  const location = firstHeader(res.headers.location);
954
1016
  if (res.statusCode >= 300 && res.statusCode < 400 && location?.includes("/market/eligibilitycheck")) {
955
- await this.perform("GET", location, opts.signal ? { signal: opts.signal } : {});
1017
+ await this.perform("GET", location, requestControl(opts));
1018
+ if (opts.noEligibilityReplay) return res;
956
1019
  return this.perform(method, url, opts);
957
1020
  }
958
1021
  return res;
@@ -1203,7 +1266,7 @@ function httpError(res, rateLimitWindowMs) {
1203
1266
  if (res.statusCode >= 300 && res.statusCode <= 399) {
1204
1267
  const loc = locationHeader(res);
1205
1268
  if (loc.includes("/login")) return new SteamSessionExpiredError();
1206
- if (loc.includes("eligibilitycheck")) return new SteamError("Steam redirected to the market eligibility check — this account is limited / not trade-eligible");
1269
+ if (loc.includes("eligibilitycheck")) return new MarketIneligibleError("Steam redirected to the market eligibility check — this account is limited / not trade-eligible");
1207
1270
  }
1208
1271
  return new HttpStatusError(res.statusCode, void 0, res.body);
1209
1272
  }
@@ -1607,8 +1670,11 @@ function xmlValue(xml, tag) {
1607
1670
  }
1608
1671
  //#endregion
1609
1672
  //#region src/http/queryTime.ts
1610
- async function queryServerTimeOffset(http) {
1611
- const res = await http.post(`${URLS.api}/ITwoFactorService/QueryTime/v1/`, { responseType: "json" });
1673
+ async function queryServerTimeOffset(http, control) {
1674
+ const res = await http.post(`${URLS.api}/ITwoFactorService/QueryTime/v1/`, {
1675
+ ...requestControl(control),
1676
+ responseType: "json"
1677
+ });
1612
1678
  if (res.statusCode !== 200) throw httpError(res);
1613
1679
  const serverTime = Number(res.body?.response?.server_time);
1614
1680
  if (!serverTime) throw new SteamError("Failed to query Steam server time");
@@ -1638,9 +1704,9 @@ var ConfirmationManager = class {
1638
1704
  const android = getDeviceID(this.steamID.getSteamID64());
1639
1705
  return this.profile.mobileClient === "ios" ? android.replace(/^android:/, "").toUpperCase() : android;
1640
1706
  }
1641
- async getConfirmations(time, key) {
1707
+ async getConfirmations(time, key, control) {
1642
1708
  const { tag, k } = splitKey(key, "conf");
1643
- const body = await this.getlist(k, time, tag);
1709
+ const body = await this.getlist(k, time, tag, control);
1644
1710
  if (!body?.success) {
1645
1711
  if (body?.needauth) throw new SteamSessionExpiredError();
1646
1712
  throw new ConfirmationError(String(body?.message ?? body?.detail ?? "Failed to get confirmation list"));
@@ -1658,13 +1724,14 @@ var ConfirmationManager = class {
1658
1724
  icon: String(c.icon ?? "")
1659
1725
  }));
1660
1726
  }
1661
- async respondToConfirmation(confID, confKey, time, key, accept) {
1727
+ async respondToConfirmation(confID, confKey, time, key, accept, control) {
1662
1728
  const ids = Array.isArray(confID) ? confID : [confID];
1663
1729
  const keys = Array.isArray(confKey) ? confKey : [confKey];
1664
1730
  if (ids.length !== keys.length) throw new ConfirmationError("confID and confKey must have the same length");
1665
1731
  const { tag, k } = splitKey(key, accept ? "allow" : "cancel");
1666
1732
  const op = accept ? "allow" : "cancel";
1667
1733
  const res = await this.http.post(`${URLS.community}/mobileconf/multiajaxop`, {
1734
+ ...requestControl(control),
1668
1735
  responseType: "json",
1669
1736
  searchParams: {
1670
1737
  ...this.confParams(k, time, tag),
@@ -1680,16 +1747,17 @@ var ConfirmationManager = class {
1680
1747
  });
1681
1748
  if (res.statusCode !== 200) throw httpError(res);
1682
1749
  if (res.body?.success) return;
1750
+ if (res.body === void 0 || res.body === null) throw new ConfirmationOutcomeUnknownError();
1683
1751
  throw new ConfirmationError(String(res.body?.message ?? "Could not act on confirmation"));
1684
1752
  }
1685
- async getPending() {
1753
+ async getPending(control) {
1686
1754
  const secret = this.requireSecret();
1687
- const listTime = time(await this.getTimeOffset());
1755
+ const listTime = time(await this.getTimeOffset(control));
1688
1756
  const listKey = getConfirmationKey(secret, listTime, "list");
1689
1757
  return this.getConfirmations(listTime, {
1690
1758
  tag: "list",
1691
1759
  key: listKey
1692
- });
1760
+ }, control);
1693
1761
  }
1694
1762
  acceptConfirmation(confID, nonce) {
1695
1763
  return this.actOnConfirmations([{
@@ -1703,11 +1771,11 @@ var ConfirmationManager = class {
1703
1771
  key: nonce
1704
1772
  }], false);
1705
1773
  }
1706
- acceptConfirmations(confs) {
1707
- return this.actOnConfirmations(confs, true);
1774
+ acceptConfirmations(confs, control) {
1775
+ return this.actOnConfirmations(confs, true, control);
1708
1776
  }
1709
- rejectConfirmations(confs) {
1710
- return this.actOnConfirmations(confs, false);
1777
+ rejectConfirmations(confs, control) {
1778
+ return this.actOnConfirmations(confs, false, control);
1711
1779
  }
1712
1780
  async acceptAll() {
1713
1781
  const pending = await this.getPending();
@@ -1719,17 +1787,17 @@ var ConfirmationManager = class {
1719
1787
  if (!conf) throw new ConfirmationError(`Could not find confirmation for object ${objectID}`);
1720
1788
  await this.acceptConfirmation(conf.id, conf.key);
1721
1789
  }
1722
- async actOnConfirmations(confs, accept) {
1790
+ async actOnConfirmations(confs, accept, control) {
1723
1791
  if (confs.length === 0) return;
1724
1792
  const secret = this.requireSecret();
1725
- const offset = await this.getTimeOffset();
1793
+ const offset = await this.getTimeOffset(control);
1726
1794
  const time = this.nextConfTime(offset);
1727
1795
  const tag = accept ? "accept" : "cancel";
1728
1796
  const key = getConfirmationKey(secret, time, tag);
1729
1797
  await this.respondToConfirmation(confs.map((c) => c.id), confs.map((c) => c.key), time, {
1730
1798
  tag,
1731
1799
  key
1732
- }, accept);
1800
+ }, accept, control);
1733
1801
  }
1734
1802
  requireSecret() {
1735
1803
  if (!this.identitySecret) throw new ConfirmationError("identitySecret is required to respond to confirmations");
@@ -1743,9 +1811,9 @@ var ConfirmationManager = class {
1743
1811
  if (this.usedConfTimes.length > 60) this.usedConfTimes.splice(0, this.usedConfTimes.length - 60);
1744
1812
  return time$1;
1745
1813
  }
1746
- async getTimeOffset() {
1814
+ async getTimeOffset(control) {
1747
1815
  if (this.timeOffset !== void 0 && Date.now() - this.timeOffsetAt < TIME_OFFSET_TTL_MS) return this.timeOffset;
1748
- const offset = await queryServerTimeOffset(this.http);
1816
+ const offset = await queryServerTimeOffset(this.http, control);
1749
1817
  this.timeOffset = offset;
1750
1818
  this.timeOffsetAt = Date.now();
1751
1819
  return offset;
@@ -1760,8 +1828,9 @@ var ConfirmationManager = class {
1760
1828
  tag
1761
1829
  };
1762
1830
  }
1763
- async getlist(key, time, tag) {
1831
+ async getlist(key, time, tag, control) {
1764
1832
  const res = await this.http.get(`${URLS.community}/mobileconf/getlist`, {
1833
+ ...requestControl(control),
1765
1834
  responseType: "json",
1766
1835
  searchParams: this.confParams(key, time, tag)
1767
1836
  });
@@ -1831,6 +1900,7 @@ var WebApiClient = class {
1831
1900
  console.log(`[http] ${httpMethod} ${url}?${qs.toString()}`);
1832
1901
  }
1833
1902
  const res = await this.http.request(httpMethod, url, {
1903
+ ...requestControl(params),
1834
1904
  responseType: "json",
1835
1905
  ...httpMethod === "GET" ? { searchParams: payload } : { form: payload }
1836
1906
  });
@@ -1930,6 +2000,101 @@ var SteamWebApi = class {
1930
2000
  }
1931
2001
  };
1932
2002
  //#endregion
2003
+ //#region src/market/fees.ts
2004
+ const DEFAULT_MARKET_FEE_INFO = Object.freeze({
2005
+ walletFee: true,
2006
+ walletFeePercent: .05,
2007
+ walletFeeMinimum: 1,
2008
+ walletFeeBase: 0,
2009
+ publisherFeePercent: .1
2010
+ });
2011
+ function assertCents(name, value) {
2012
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative integer number of cents, got ${value}`);
2013
+ }
2014
+ const MAX_FEE_FRACTION = .5;
2015
+ function assertFeeInfo(info) {
2016
+ const fraction = (v) => Number.isFinite(v) && v >= 0 && v < MAX_FEE_FRACTION;
2017
+ const cents = (v) => Number.isSafeInteger(v) && v >= 0;
2018
+ if (typeof info?.walletFee !== "boolean" || !fraction(info.walletFeePercent) || !fraction(info.publisherFeePercent) || !cents(info.walletFeeMinimum) || !cents(info.walletFeeBase)) throw new RangeError(`invalid market fee info: ${JSON.stringify(info)}`);
2019
+ }
2020
+ function feeInfoFromWalletInfo(raw, publisherFeePercent) {
2021
+ const num = (v) => typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
2022
+ const info = {
2023
+ walletFee: num(raw.wallet_fee) === 1,
2024
+ walletFeePercent: num(raw.wallet_fee_percent),
2025
+ walletFeeMinimum: num(raw.wallet_fee_minimum),
2026
+ walletFeeBase: num(raw.wallet_fee_base),
2027
+ publisherFeePercent: publisherFeePercent ?? num(raw.wallet_publisher_fee_percent_default)
2028
+ };
2029
+ if (num(raw.wallet_fee) !== 0 && num(raw.wallet_fee) !== 1) return null;
2030
+ try {
2031
+ assertFeeInfo(info);
2032
+ } catch {
2033
+ return null;
2034
+ }
2035
+ return info;
2036
+ }
2037
+ function buyerPaysForSellerReceives(receivedAmount, info) {
2038
+ assertCents("receivedAmount", receivedAmount);
2039
+ assertFeeInfo(info);
2040
+ return amountToSend(receivedAmount, info);
2041
+ }
2042
+ function feesForBuyerPays(amount, info) {
2043
+ assertCents("amount", amount);
2044
+ assertFeeInfo(info);
2045
+ if (!info.walletFee) return {
2046
+ amount,
2047
+ steamFee: 0,
2048
+ publisherFee: 0,
2049
+ fees: 0
2050
+ };
2051
+ let iterations = 0;
2052
+ let estimate = Math.trunc((amount - Math.trunc(info.walletFeeBase)) / (info.walletFeePercent + info.publisherFeePercent + 1));
2053
+ let everUndershot = false;
2054
+ let fees = amountToSend(estimate, info);
2055
+ while (fees.amount !== amount && iterations < 10) {
2056
+ if (fees.amount > amount) {
2057
+ if (everUndershot) {
2058
+ fees = amountToSend(estimate - 1, info);
2059
+ fees.steamFee += amount - fees.amount;
2060
+ fees.fees += amount - fees.amount;
2061
+ fees.amount = amount;
2062
+ break;
2063
+ }
2064
+ estimate--;
2065
+ } else {
2066
+ everUndershot = true;
2067
+ estimate++;
2068
+ }
2069
+ fees = amountToSend(estimate, info);
2070
+ iterations++;
2071
+ }
2072
+ return fees;
2073
+ }
2074
+ function sellerReceivesForBuyerPays(amount, info) {
2075
+ return Math.max(0, amount - feesForBuyerPays(amount, info).fees);
2076
+ }
2077
+ function landingBuyerPays(targetBuyerPays, info) {
2078
+ const receives = sellerReceivesForBuyerPays(targetBuyerPays, info);
2079
+ return receives === 0 ? 0 : buyerPaysForSellerReceives(receives, info).amount;
2080
+ }
2081
+ function amountToSend(receivedAmount, info) {
2082
+ if (!info.walletFee) return {
2083
+ amount: receivedAmount,
2084
+ steamFee: 0,
2085
+ publisherFee: 0,
2086
+ fees: 0
2087
+ };
2088
+ const steamFee = Math.trunc(Math.floor(Math.max(receivedAmount * info.walletFeePercent, info.walletFeeMinimum) + Math.trunc(info.walletFeeBase)));
2089
+ const publisherFee = Math.trunc(Math.floor(info.publisherFeePercent > 0 ? Math.max(receivedAmount * info.publisherFeePercent, 1) : 0));
2090
+ return {
2091
+ amount: Math.trunc(receivedAmount + steamFee + publisherFee),
2092
+ steamFee,
2093
+ publisherFee,
2094
+ fees: steamFee + publisherFee
2095
+ };
2096
+ }
2097
+ //#endregion
1933
2098
  //#region src/market/prices.ts
1934
2099
  function getPriceValueAsInt(strAmount) {
1935
2100
  if (!strAmount) return 0;
@@ -1954,6 +2119,7 @@ function parseMarketHistory(body) {
1954
2119
  sales: [],
1955
2120
  purchases: [],
1956
2121
  listingEvents: [],
2122
+ malformed: [],
1957
2123
  totalCount: body.total_count
1958
2124
  };
1959
2125
  if (!body.results_html) return history;
@@ -1969,11 +2135,19 @@ function parseMarketHistory(body) {
1969
2135
  const displayPrice = textOf(row.inner, "market_listing_price").trim();
1970
2136
  const rowId = attrValue(row.attrs, "id") ?? "";
1971
2137
  const priceInCents = getPriceValueAsInt(displayPrice);
1972
- if (priceInCents === 0 && displayPrice) throw new SteamError(`Market history price parsed to 0 cents for "${displayPrice}" in row ${rowId || "?"}`);
1973
2138
  const gainOrLoss = textOf(row.inner, "market_listing_gainorloss").trim();
1974
2139
  const type = gainOrLoss === "-" ? "sale" : gainOrLoss === "+" ? "purchase" : "listing_event";
1975
2140
  const idMatch = rowId.match(/^history_row_(\d+)_(\d+)$/);
1976
2141
  if (!idMatch) continue;
2142
+ if (priceInCents === 0 && (displayPrice || type !== "listing_event")) {
2143
+ history.malformed.push({
2144
+ historyId: rowId,
2145
+ displayPrice,
2146
+ gainOrLoss,
2147
+ reason: displayPrice ? "zero_price" : "missing_price"
2148
+ });
2149
+ continue;
2150
+ }
1977
2151
  const event = {
1978
2152
  historyId: rowId,
1979
2153
  listingid: idMatch[1],
@@ -2107,23 +2281,56 @@ function decodeEntities(s) {
2107
2281
  return NAMED_ENTITIES[ref.toLowerCase()] ?? whole;
2108
2282
  });
2109
2283
  }
2284
+ function priceTextCurrency(text) {
2285
+ const t = text.trim();
2286
+ if (t.includes("€")) return t.includes("$") ? void 0 : 3;
2287
+ const dollar = t.indexOf("$");
2288
+ if (dollar === -1 || t.indexOf("$", dollar + 1) !== -1) return void 0;
2289
+ if (/[A-Za-z]$/.test(t.slice(0, dollar)) || /^[A-Za-z]/.test(t.slice(dollar + 1))) return;
2290
+ return 1;
2291
+ }
2110
2292
  //#endregion
2111
2293
  //#region src/market/MarketNamespace.ts
2112
2294
  const LISTINGS_PAGE_SIZE = 100;
2113
2295
  const MARKET_REFERER = `${URLS.community}/market/`;
2296
+ const LISTING_CURRENCY_BASE = 2e3;
2297
+ const BUY_ORDER_CONFIRMATION_LOOKUPS = [
2298
+ 0,
2299
+ 1e3,
2300
+ 2500
2301
+ ];
2302
+ const BUY_ORDER_NOT_PLACED_CODES = /* @__PURE__ */ new Set([
2303
+ 15,
2304
+ 25,
2305
+ 107
2306
+ ]);
2307
+ const UNKNOWN_OUTCOME_RE = /hearing back|double check/i;
2308
+ const DUPLICATE_ORDER_CODE = 29;
2114
2309
  var MarketNamespace = class {
2115
2310
  http;
2116
2311
  session;
2117
2312
  confirmations;
2118
2313
  api;
2119
- walletCurrency;
2314
+ currency;
2315
+ currencyVerified = false;
2120
2316
  constructor(http, session, confirmations, api) {
2121
2317
  this.http = http;
2122
2318
  this.session = session;
2123
2319
  this.confirmations = confirmations;
2124
2320
  this.api = api;
2125
2321
  }
2126
- async getWalletDetails() {
2322
+ get walletCurrency() {
2323
+ return this.currency;
2324
+ }
2325
+ get walletCurrencyVerified() {
2326
+ return this.currencyVerified;
2327
+ }
2328
+ seedWalletCurrency(currency) {
2329
+ if (!Number.isInteger(currency) || currency <= 0) throw new RangeError(`walletCurrency must be a positive integer, got ${currency}`);
2330
+ if (this.currency !== void 0 && this.currency !== currency) throw new CurrencyMismatchError(this.currency, currency, "seedWalletCurrency");
2331
+ this.currency = currency;
2332
+ }
2333
+ async getWalletDetails(options = {}) {
2127
2334
  const body = await this.api.call({
2128
2335
  httpMethod: "POST",
2129
2336
  iface: "IUserAccountService",
@@ -2131,28 +2338,54 @@ var MarketNamespace = class {
2131
2338
  input: {
2132
2339
  include_balance_in_usd: 1,
2133
2340
  include_formatted_balance: 1
2134
- }
2341
+ },
2342
+ ...requestControl(options)
2135
2343
  });
2136
2344
  if (!body.response) throw new SteamError("Malformed wallet details response", { body });
2137
2345
  const currency = body.response.currency_code;
2138
- if (typeof currency === "number" && currency > 0) this.walletCurrency = currency;
2346
+ if (body.response.has_wallet === true && typeof currency === "number" && currency > 0) this.learnCurrency(currency, "wallet details");
2139
2347
  return body.response;
2140
2348
  }
2349
+ async getMarketWalletInfo(options = {}) {
2350
+ await this.session.getAccessToken();
2351
+ const res = await this.http.get(`${URLS.community}/market/`, requestControl(options));
2352
+ if (res.statusCode !== 200) throw httpError(res);
2353
+ if (res.body.match(/g_steamID\s*=\s*"(\d+)"/)?.[1] !== this.session.steamID.getSteamID64()) throw new SteamSessionExpiredError("market page was not rendered for this account");
2354
+ const match = res.body.match(/g_rgWalletInfo\s*=\s*(\{[^;]*?\});/);
2355
+ if (!match?.[1]) throw new SteamError("g_rgWalletInfo not found on the market page");
2356
+ let info;
2357
+ try {
2358
+ info = JSON.parse(match[1]);
2359
+ } catch {
2360
+ throw new SteamError("g_rgWalletInfo on the market page is not valid JSON");
2361
+ }
2362
+ const currency = Number(info.wallet_currency);
2363
+ if (this.currency !== void 0 && currency !== this.currency) throw new CurrencyMismatchError(this.currency, currency, "g_rgWalletInfo");
2364
+ return info;
2365
+ }
2141
2366
  async getMyListings(options = {}) {
2367
+ const currency = this.readCurrency(options.currency, "mylistings");
2142
2368
  await this.session.getAccessToken();
2143
2369
  const result = {
2144
2370
  listings: [],
2145
2371
  listings_to_confirm: [],
2372
+ listings_on_hold: [],
2146
2373
  buy_orders: [],
2147
- num_active_listings: 0
2374
+ num_active_listings: 0,
2375
+ complete: false
2148
2376
  };
2377
+ const seen = /* @__PURE__ */ new Set();
2378
+ let firstTotal;
2379
+ let totalMoved = false;
2380
+ let buyOrdersMissing = false;
2149
2381
  let start = 0;
2150
2382
  for (;;) {
2151
2383
  const res = await this.http.get(`${URLS.community}/market/mylistings/`, {
2384
+ ...requestControl(options),
2152
2385
  responseType: "json",
2153
2386
  searchParams: {
2154
2387
  norender: 1,
2155
- currency: options.currency ?? this.walletCurrency,
2388
+ currency: currency ?? void 0,
2156
2389
  start,
2157
2390
  count: LISTINGS_PAGE_SIZE
2158
2391
  }
@@ -2160,20 +2393,47 @@ var MarketNamespace = class {
2160
2393
  if (res.statusCode !== 200) throw httpError(res);
2161
2394
  const body = res.body;
2162
2395
  if (!body?.success) throw new SteamError("Steam returned success=false for /market/mylistings", { body });
2163
- if (body.num_active_listings !== void 0) result.num_active_listings = body.num_active_listings;
2396
+ if (body.num_active_listings !== void 0) {
2397
+ result.num_active_listings = body.num_active_listings;
2398
+ if (firstTotal === void 0) firstTotal = body.num_active_listings;
2399
+ else if (body.num_active_listings !== firstTotal) totalMoved = true;
2400
+ }
2164
2401
  if (start === 0) {
2165
2402
  if (Array.isArray(body.buy_orders)) result.buy_orders = body.buy_orders;
2403
+ else buyOrdersMissing = true;
2166
2404
  if (Array.isArray(body.listings_to_confirm)) result.listings_to_confirm = body.listings_to_confirm;
2405
+ if (Array.isArray(body.listings_on_hold)) result.listings_on_hold = body.listings_on_hold;
2167
2406
  }
2168
2407
  const page = body.listings ?? body.results?.listings ?? [];
2169
- result.listings.push(...page);
2408
+ for (const listing of page) {
2409
+ if (seen.has(listing.listingid)) continue;
2410
+ seen.add(listing.listingid);
2411
+ result.listings.push(listing);
2412
+ }
2170
2413
  start += page.length;
2171
- if (start >= result.num_active_listings || page.length === 0) return result;
2414
+ if (start >= result.num_active_listings || page.length === 0) break;
2172
2415
  }
2416
+ result.complete = !totalMoved && !buyOrdersMissing && result.listings.length === result.num_active_listings;
2417
+ if (currency !== null) {
2418
+ for (const order of result.buy_orders) {
2419
+ const actual = Number(order.wallet_currency);
2420
+ if (actual !== currency) throw new CurrencyMismatchError(currency, actual, "buy orders");
2421
+ }
2422
+ const all = [
2423
+ ...result.listings,
2424
+ ...result.listings_to_confirm,
2425
+ ...result.listings_on_hold
2426
+ ];
2427
+ for (const listing of all) assertListingCurrency(listing, currency);
2428
+ if (result.buy_orders.length > 0 || all.length > 0) this.confirmCurrency(currency);
2429
+ }
2430
+ return result;
2173
2431
  }
2174
2432
  async getMyHistory(options = {}) {
2433
+ const currency = this.readCurrency(options.currency, "myhistory");
2175
2434
  await this.session.getAccessToken();
2176
2435
  const res = await this.http.get(`${URLS.community}/market/myhistory`, {
2436
+ ...requestControl(options),
2177
2437
  responseType: "json",
2178
2438
  searchParams: {
2179
2439
  count: options.count ?? 500,
@@ -2182,49 +2442,92 @@ var MarketNamespace = class {
2182
2442
  });
2183
2443
  if (res.statusCode !== 200) throw httpError(res);
2184
2444
  if (!res.body?.success) throw new SteamError("Steam returned success=false for /market/myhistory", { body: res.body });
2185
- return parseMarketHistory(res.body);
2445
+ const history = parseMarketHistory(res.body);
2446
+ if (currency !== null && PINNED_PRICE_FORMATS.has(currency)) {
2447
+ const other = new Set(history.events.filter((e) => e.displayPrice && priceTextCurrency(e.displayPrice) !== currency).map((e) => e.historyId));
2448
+ if (other.size > 0) {
2449
+ for (const e of history.events) {
2450
+ if (!other.has(e.historyId)) continue;
2451
+ history.malformed.push({
2452
+ historyId: e.historyId,
2453
+ displayPrice: e.displayPrice,
2454
+ gainOrLoss: e.type === "sale" ? "-" : e.type === "purchase" ? "+" : "",
2455
+ reason: "other_currency"
2456
+ });
2457
+ }
2458
+ const keep = (rows) => rows.filter((r) => !other.has(r.historyId));
2459
+ history.events = keep(history.events);
2460
+ history.sales = keep(history.sales);
2461
+ history.purchases = keep(history.purchases);
2462
+ history.listingEvents = keep(history.listingEvents);
2463
+ }
2464
+ }
2465
+ return history;
2186
2466
  }
2187
2467
  async getOrderbook(appid, marketHashName, options = {}) {
2468
+ const expected = this.readCurrency(options.expectedCurrency, "orderbook");
2188
2469
  await this.session.getAccessToken();
2189
2470
  const qp = encodeURIComponent(JSON.stringify([appid, marketHashName]));
2190
- const res = await this.http.get(`${URLS.community}/market/orderbook?q=Load&qp=${qp}`, { responseType: "json" });
2471
+ const res = await this.http.get(`${URLS.community}/market/orderbook?q=Load&qp=${qp}`, {
2472
+ ...requestControl(options),
2473
+ responseType: "json"
2474
+ });
2191
2475
  if (res.statusCode === 403) throw new ThrottledError(void 0, { body: res.body });
2192
2476
  if (res.statusCode !== 200) throw httpError(res);
2193
2477
  const body = res.body;
2194
2478
  const payload = body && body.success === void 0 && body.data ? body.data : body;
2195
2479
  if (!payload?.success || !payload.data) throw new SteamError(`Bad orderbook response for ${marketHashName}`, { body });
2196
2480
  const data = payload.data;
2197
- const expected = options.expectedCurrency ?? this.walletCurrency;
2198
- if (expected && data.eCurrency !== expected) throw new SteamError(`Orderbook currency ${data.eCurrency} != expected ${expected} for ${marketHashName}`, { body });
2481
+ if (expected !== null) {
2482
+ if (data.eCurrency !== expected) throw new CurrencyMismatchError(expected, data.eCurrency, `orderbook of ${marketHashName}`);
2483
+ this.confirmCurrency(expected);
2484
+ }
2199
2485
  return data;
2200
2486
  }
2201
- async getPriceHistory(appid, marketHashName) {
2487
+ async getPriceHistory(appid, marketHashName, options = {}) {
2488
+ const expected = this.readCurrency(options.expectedCurrency, "pricehistory");
2202
2489
  await this.session.getAccessToken();
2203
- const res = await this.http.get(`${URLS.community}/market/pricehistory?appid=${appid}&market_hash_name=${encodeURIComponent(marketHashName)}`, { responseType: "json" });
2490
+ const res = await this.http.get(`${URLS.community}/market/pricehistory?appid=${appid}&market_hash_name=${encodeURIComponent(marketHashName)}`, {
2491
+ ...requestControl(options),
2492
+ responseType: "json"
2493
+ });
2204
2494
  if (res.statusCode !== 200) throw httpError(res);
2205
2495
  if (!res.body?.success) throw new SteamError(`Steam returned success=false for pricehistory of ${marketHashName}`, { body: res.body });
2496
+ if (expected !== null && PINNED_PRICE_FORMATS.has(expected)) {
2497
+ const actual = priceHistoryCurrency(res.body.price_prefix, res.body.price_suffix);
2498
+ if (actual !== expected) throw new CurrencyMismatchError(expected, actual, `pricehistory of ${marketHashName}`);
2499
+ }
2206
2500
  return Array.isArray(res.body.prices) ? res.body.prices : [];
2207
2501
  }
2208
- async getMarketItemDetails(appid, marketHashName) {
2502
+ async getMarketItemDetails(appid, marketHashName, options = {}) {
2209
2503
  await this.session.getAccessToken();
2210
2504
  const query = encodeURIComponent(`"${marketHashName}"`);
2211
- const res = await this.http.get(`${URLS.community}/market/search/render/?query=${query}&start=0&count=10&search_descriptions=0&sort_column=quantity&sort_dir=desc&appid=${appid}&norender=1`, { responseType: "json" });
2505
+ const res = await this.http.get(`${URLS.community}/market/search/render/?query=${query}&start=0&count=10&search_descriptions=0&sort_column=quantity&sort_dir=desc&appid=${appid}&norender=1`, {
2506
+ ...requestControl(options),
2507
+ responseType: "json"
2508
+ });
2212
2509
  if (res.statusCode !== 200) throw httpError(res);
2213
2510
  const body = res.body;
2214
- if (!body || typeof body !== "object") throw new SteamError("Malformed market search response", { body });
2511
+ if (!body || typeof body !== "object" || body.success === false) throw new SteamError("Malformed or unsuccessful market search response", { body });
2215
2512
  return (Array.isArray(body.results) ? body.results : []).find((r) => r.hash_name === marketHashName) ?? null;
2216
2513
  }
2217
2514
  async sellItem(options) {
2515
+ this.writeCurrency(options.currency, "sellitem");
2516
+ assertPositiveCents("price", options.price);
2517
+ const amount = options.amount ?? 1;
2518
+ assertPositiveCents("amount", amount);
2218
2519
  await this.session.getAccessToken();
2219
2520
  const sessionid = await this.http.getSessionId();
2220
2521
  const res = await this.http.post(`${URLS.community}/market/sellitem/`, {
2522
+ ...requestControl(options),
2523
+ noEligibilityReplay: true,
2221
2524
  responseType: "json",
2222
2525
  form: {
2223
2526
  sessionid,
2224
2527
  appid: options.appid,
2225
2528
  contextid: options.contextid ?? "2",
2226
2529
  assetid: options.assetid,
2227
- amount: options.amount ?? 1,
2530
+ amount,
2228
2531
  price: options.price
2229
2532
  },
2230
2533
  headers: { Referer: MARKET_REFERER }
@@ -2239,10 +2542,12 @@ var MarketNamespace = class {
2239
2542
  body
2240
2543
  });
2241
2544
  }
2242
- async cancelListing(listingId) {
2545
+ async cancelListing(listingId, options = {}) {
2243
2546
  await this.session.getAccessToken();
2244
2547
  const sessionid = await this.http.getSessionId();
2245
2548
  const res = await this.http.post(`${URLS.community}/market/removelisting/${listingId}`, {
2549
+ ...requestControl(options),
2550
+ noEligibilityReplay: true,
2246
2551
  form: { sessionid },
2247
2552
  headers: { Referer: MARKET_REFERER }
2248
2553
  });
@@ -2250,8 +2555,10 @@ var MarketNamespace = class {
2250
2555
  throw httpError(res);
2251
2556
  }
2252
2557
  async createBuyOrder(options) {
2253
- const currency = options.currency ?? this.walletCurrency;
2254
- if (!currency) throw new SteamError("Wallet currency unknown — call getWalletDetails() or pass currency");
2558
+ const currency = this.writeCurrency(options.currency, "createbuyorder");
2559
+ assertPositiveCents("quantity", options.quantity);
2560
+ assertPositiveCents("priceTotal", options.priceTotal);
2561
+ if (options.priceTotal % options.quantity !== 0) throw new RangeError("priceTotal must be the per-unit price times quantity");
2255
2562
  await this.session.getAccessToken();
2256
2563
  const sessionid = await this.http.getSessionId();
2257
2564
  const referer = `${URLS.community}/market/listings/${options.appid}/${encodeURIComponent(options.marketHashName)}`;
@@ -2264,24 +2571,44 @@ var MarketNamespace = class {
2264
2571
  quantity: options.quantity,
2265
2572
  confirmation: 0
2266
2573
  };
2267
- const first = await this.postBuyOrder(form, referer);
2268
- const direct = buyOrderId(first);
2269
- if (direct) return direct;
2270
- const confirmationId = first.body?.confirmation?.confirmation_id;
2271
- if (!confirmationId) throw buyOrderError(first, `No confirmation_id (HTTP ${first.statusCode})`);
2272
- await this.confirmations.acceptConfirmationForObject(String(confirmationId));
2273
- const final = await this.postBuyOrder({
2274
- ...form,
2275
- confirmation: String(confirmationId)
2276
- }, referer);
2277
- const orderId = buyOrderId(final);
2278
- if (orderId) return orderId;
2279
- throw buyOrderError(final, `Finalization failed (HTTP ${final.statusCode})`);
2280
- }
2281
- async cancelBuyOrder(buyOrderId) {
2574
+ let first;
2575
+ try {
2576
+ first = await this.postBuyOrder(form, referer, options);
2577
+ } catch (err) {
2578
+ if (isNotPlacedError(err)) throw err;
2579
+ throw new BuyOrderError("createbuyorder request failed", {
2580
+ stage: "create",
2581
+ mayExist: true,
2582
+ cause: err
2583
+ });
2584
+ }
2585
+ if (first.buyOrderId) return first.buyOrderId;
2586
+ const confirmationId = first.confirmationId;
2587
+ if (!confirmationId) throw buyOrderRefusal(first, "create");
2588
+ await this.acceptBuyOrderConfirmation(confirmationId, options);
2589
+ let final;
2590
+ try {
2591
+ final = await this.postBuyOrder({
2592
+ ...form,
2593
+ confirmation: confirmationId
2594
+ }, referer, options);
2595
+ } catch (err) {
2596
+ throw new BuyOrderError("createbuyorder finalize request failed", {
2597
+ stage: "finalize",
2598
+ mayExist: true,
2599
+ confirmationId,
2600
+ cause: err
2601
+ });
2602
+ }
2603
+ if (final.buyOrderId) return final.buyOrderId;
2604
+ throw buyOrderRefusal(final, "finalize", confirmationId);
2605
+ }
2606
+ async cancelBuyOrder(buyOrderId, options = {}) {
2282
2607
  await this.session.getAccessToken();
2283
2608
  const sessionid = await this.http.getSessionId();
2284
2609
  const res = await this.http.post(`${URLS.community}/market/cancelbuyorder/`, {
2610
+ ...requestControl(options),
2611
+ noEligibilityReplay: true,
2285
2612
  responseType: "json",
2286
2613
  form: {
2287
2614
  sessionid,
@@ -2290,15 +2617,35 @@ var MarketNamespace = class {
2290
2617
  headers: { Referer: MARKET_REFERER }
2291
2618
  });
2292
2619
  if (res.statusCode !== 200) throw httpError(res);
2293
- return res.body ?? {};
2620
+ const body = res.body ?? {};
2621
+ if (body.success === 1 || body.success === true) return body;
2622
+ throw new SteamError(`cancelbuyorder refused for ${buyOrderId}`, {
2623
+ ...eresultOf(body),
2624
+ body
2625
+ });
2294
2626
  }
2295
- async confirmListings(expected) {
2627
+ async confirmListingIds(listingIds, options = {}) {
2628
+ const wanted = new Set(listingIds.map(String));
2629
+ if (wanted.size === 0) return {
2630
+ confirmed: [],
2631
+ missing: []
2632
+ };
2633
+ await this.session.getAccessToken();
2634
+ const confirmed = (await this.confirmations.getPending(options)).filter((c) => c.type === 3 && wanted.has(c.creator));
2635
+ await this.confirmations.acceptConfirmations(confirmed, options);
2636
+ const found = new Set(confirmed.map((c) => c.creator));
2637
+ return {
2638
+ confirmed,
2639
+ missing: [...wanted].filter((id) => !found.has(id))
2640
+ };
2641
+ }
2642
+ async confirmListings(expected, options = {}) {
2296
2643
  if (Object.values(expected).reduce((sum, n) => sum + n, 0) === 0) return {
2297
2644
  confirmed: [],
2298
2645
  skipped: []
2299
2646
  };
2300
2647
  await this.session.getAccessToken();
2301
- const listings = (await this.confirmations.getPending()).filter((c) => c.type === 3).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
2648
+ const listings = (await this.confirmations.getPending(options)).filter((c) => c.type === 3).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
2302
2649
  const remaining = { ...expected };
2303
2650
  const confirmed = [];
2304
2651
  const skipped = [];
@@ -2311,28 +2658,159 @@ var MarketNamespace = class {
2311
2658
  confirmed.push(conf);
2312
2659
  remaining[name] = (remaining[name] ?? 0) - 1;
2313
2660
  }
2314
- await this.confirmations.acceptConfirmations(confirmed);
2661
+ await this.confirmations.acceptConfirmations(confirmed, options);
2315
2662
  return {
2316
2663
  confirmed,
2317
2664
  skipped
2318
2665
  };
2319
2666
  }
2320
- async rejectListings(types = [3]) {
2667
+ async rejectListings(types = [3], options = {}) {
2321
2668
  await this.session.getAccessToken();
2322
- const matched = (await this.confirmations.getPending()).filter((c) => types.includes(c.type));
2323
- await this.confirmations.rejectConfirmations(matched);
2669
+ const keep = new Set((options.keepCreators ?? []).map(String));
2670
+ const matched = (await this.confirmations.getPending(options)).filter((c) => types.includes(c.type) && !keep.has(c.creator));
2671
+ await this.confirmations.rejectConfirmations(matched, options);
2324
2672
  return matched;
2325
2673
  }
2326
- async postBuyOrder(form, referer) {
2674
+ async acceptBuyOrderConfirmation(confirmationId, options) {
2675
+ let conf;
2676
+ try {
2677
+ for (const delay of BUY_ORDER_CONFIRMATION_LOOKUPS) {
2678
+ if (delay > 0) await sleep(delay, options.signal);
2679
+ conf = (await this.confirmations.getPending(options)).find((c) => c.creator === confirmationId && c.type === 12);
2680
+ if (conf) break;
2681
+ }
2682
+ } catch (err) {
2683
+ throw new BuyOrderError("could not read the buy order's mobile confirmation", {
2684
+ stage: "confirm",
2685
+ mayExist: false,
2686
+ confirmationId,
2687
+ cause: err
2688
+ });
2689
+ }
2690
+ if (!conf) throw new BuyOrderError(`no mobile confirmation found for buy order ${confirmationId}`, {
2691
+ stage: "confirm",
2692
+ mayExist: false,
2693
+ confirmationId
2694
+ });
2695
+ try {
2696
+ await this.confirmations.acceptConfirmations([conf], options);
2697
+ } catch (err) {
2698
+ throw new BuyOrderError("accepting the buy order's mobile confirmation failed", {
2699
+ stage: "confirm",
2700
+ mayExist: !(err instanceof ConfirmationError && !(err instanceof ConfirmationOutcomeUnknownError)),
2701
+ confirmationId,
2702
+ cause: err
2703
+ });
2704
+ }
2705
+ }
2706
+ async postBuyOrder(form, referer, control) {
2327
2707
  const res = await this.http.post(`${URLS.community}/market/createbuyorder/`, {
2328
- responseType: "json",
2708
+ ...requestControl(control),
2709
+ noEligibilityReplay: true,
2329
2710
  form,
2330
2711
  headers: { Referer: referer }
2331
2712
  });
2332
2713
  if (res.statusCode === 429 || res.statusCode === 401) throw httpError(res);
2333
- return res;
2714
+ const raw = typeof res.body === "string" ? res.body : "";
2715
+ const body = safeJson(raw);
2716
+ const ok = body?.success === 1 || body?.success === true || body?.success === void 0 && body?.wallet_info?.success === 1;
2717
+ return {
2718
+ res,
2719
+ body,
2720
+ buyOrderId: res.statusCode === 200 && ok ? rawIdField(raw, "buy_orderid") ?? void 0 : void 0,
2721
+ confirmationId: rawIdField(raw, "confirmation_id") ?? void 0
2722
+ };
2723
+ }
2724
+ readCurrency(explicit, context) {
2725
+ if (explicit === null) return null;
2726
+ const known = this.currency;
2727
+ if (explicit !== void 0) {
2728
+ if (known !== void 0 && explicit !== known) throw new CurrencyMismatchError(known, explicit, context);
2729
+ return explicit;
2730
+ }
2731
+ if (known === void 0) throw new WalletCurrencyUnknownError(context);
2732
+ return known;
2733
+ }
2734
+ writeCurrency(explicit, context) {
2735
+ const known = this.currency;
2736
+ if (known === void 0 || !this.currencyVerified) throw new WalletCurrencyUnknownError(context);
2737
+ if (explicit !== void 0 && explicit !== known) throw new CurrencyMismatchError(known, explicit, context);
2738
+ return known;
2739
+ }
2740
+ learnCurrency(currency, context) {
2741
+ if (this.currency !== void 0 && this.currency !== currency) throw new CurrencyMismatchError(this.currency, currency, context);
2742
+ this.currency = currency;
2743
+ this.currencyVerified = true;
2744
+ }
2745
+ confirmCurrency(currency) {
2746
+ if (this.currency === currency) this.currencyVerified = true;
2334
2747
  }
2335
2748
  };
2749
+ function summarizeOrderbook(data) {
2750
+ const sells = depth(data.rgCompactSellOrders, "asc");
2751
+ const buys = depth(data.rgCompactBuyOrders, "desc");
2752
+ return {
2753
+ currency: data.eCurrency,
2754
+ lowestAsk: level(data.amtMinSellOrder, sells),
2755
+ askVolumeAtLowest: sells[1] ?? 0,
2756
+ secondLowestAsk: sells.length >= 4 ? sells[2] ?? null : null,
2757
+ highestBid: level(data.amtMaxBuyOrder, buys),
2758
+ bidVolumeAtHighest: buys[1] ?? 0,
2759
+ secondHighestBid: buys.length >= 4 ? buys[2] ?? null : null
2760
+ };
2761
+ }
2762
+ function wholeCents(v) {
2763
+ if (typeof v === "number") return Number.isSafeInteger(v) ? v : null;
2764
+ if (typeof v === "string" && /^\d+$/.test(v)) {
2765
+ const n = Number(v);
2766
+ return Number.isSafeInteger(n) ? n : null;
2767
+ }
2768
+ return null;
2769
+ }
2770
+ function depth(raw, order) {
2771
+ if (raw === void 0 || raw === null) return [];
2772
+ if (!Array.isArray(raw) || raw.length % 2 !== 0) throw new SteamError("Malformed orderbook depth", { body: raw });
2773
+ const out = [];
2774
+ for (const v of raw) {
2775
+ const n = wholeCents(v);
2776
+ if (n === null || n <= 0) throw new SteamError("Malformed orderbook depth", { body: raw });
2777
+ out.push(n);
2778
+ }
2779
+ for (let i = 2; i < out.length; i += 2) {
2780
+ const prev = out[i - 2];
2781
+ const cur = out[i];
2782
+ if (order === "asc" ? cur <= prev : cur >= prev) throw new SteamError("Orderbook depth is not strictly ordered", { body: raw });
2783
+ }
2784
+ return out;
2785
+ }
2786
+ function level(best, levels) {
2787
+ if (best === null || best === void 0) {
2788
+ if (levels.length > 0) throw new SteamError("Orderbook has depth but no best level");
2789
+ return null;
2790
+ }
2791
+ const value = wholeCents(best);
2792
+ if (value === null || value <= 0) throw new SteamError(`Malformed orderbook best level ${best}`);
2793
+ if (levels.length === 0 || levels[0] !== value) throw new SteamError(`Orderbook best level ${value} disagrees with its depth ${levels[0]}`);
2794
+ return value;
2795
+ }
2796
+ const PINNED_PRICE_FORMATS = /* @__PURE__ */ new Set([1, 3]);
2797
+ function priceHistoryCurrency(prefix, suffix) {
2798
+ const p = typeof prefix === "string" ? prefix.trim() : "";
2799
+ const s = typeof suffix === "string" ? suffix.trim() : "";
2800
+ if (p === "$" && s === "") return 1;
2801
+ if (p === "" && s === "€") return 3;
2802
+ }
2803
+ function assertListingCurrency(listing, currency) {
2804
+ const raw = listing.currencyid;
2805
+ const actual = raw === void 0 || raw === null ? void 0 : Number(raw) - LISTING_CURRENCY_BASE;
2806
+ if (actual !== currency) throw new CurrencyMismatchError(currency, actual, `listing ${listing.listingid}`);
2807
+ }
2808
+ function assertPositiveCents(name, value) {
2809
+ if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive integer, got ${value}`);
2810
+ }
2811
+ function isNotPlacedError(err) {
2812
+ return err instanceof RateLimitError || err instanceof SteamSessionExpiredError || err instanceof MarketIneligibleError;
2813
+ }
2336
2814
  function redirectsToMarket(res) {
2337
2815
  const location = res.headers.location;
2338
2816
  if (typeof location !== "string" || !URL.canParse(location, URLS.community)) return false;
@@ -2349,24 +2827,71 @@ function sellItemError(message, body) {
2349
2827
  body
2350
2828
  };
2351
2829
  const lower = message.toLowerCase();
2352
- if (lower.includes("pending confirmation")) return new MarketConfirmationLimitError(message, options);
2830
+ if (lower.includes("pending confirmation")) {
2831
+ const reason = lower.includes("already have a listing") ? "item_pending" : lower.includes("too many") ? "too_many_pending" : "unknown";
2832
+ return new MarketConfirmationLimitError(message, {
2833
+ ...options,
2834
+ reason
2835
+ });
2836
+ }
2353
2837
  if (lower.includes("maximum wallet balance")) return new WalletBalanceLimitError(message, options);
2354
2838
  if (lower.includes("unable to use the community market")) return new MarketBlockedError(message, options);
2839
+ if (lower.includes("no longer in your inventory") || lower.includes("not allowed to be traded")) return new MarketItemUnavailableError(message, options);
2355
2840
  return new SteamError(message, options);
2356
2841
  }
2357
- function buyOrderId(res) {
2358
- const body = res.body;
2359
- const ok = body?.success === 1 || body?.success === true || body?.wallet_info?.success === 1;
2360
- return res.statusCode === 200 && ok && body?.buy_orderid ? String(body.buy_orderid) : void 0;
2361
- }
2362
- function buyOrderError(res, fallback) {
2363
- const body = res.body;
2364
- const eresult = eresultOf(body);
2365
- if (!body?.message && eresult.eresult === void 0 && ![200, 406].includes(res.statusCode)) return httpError(res);
2366
- return new SteamError(body?.message || fallback, {
2367
- ...eresult,
2842
+ function buyOrderRefusal(reply, stage, confirmationId) {
2843
+ const { res, body } = reply;
2844
+ const code = typeof body?.success === "number" && body.success !== 1 ? body.success : void 0;
2845
+ const message = typeof body?.message === "string" ? body.message : void 0;
2846
+ if (code === 15 || message?.toLowerCase().includes("unable to use the community market")) return new MarketBlockedError(message, {
2847
+ ...code !== void 0 ? { eresult: code } : {},
2368
2848
  body
2369
2849
  });
2850
+ if (code === void 0 && message === void 0 && stage === "create") {
2851
+ if (![200, 406].includes(res.statusCode)) {
2852
+ const transport = httpError(res);
2853
+ if (isNotPlacedError(transport)) return transport;
2854
+ }
2855
+ }
2856
+ const unknownOutcome = message !== void 0 && UNKNOWN_OUTCOME_RE.test(message);
2857
+ const notPlaced = code !== void 0 && BUY_ORDER_NOT_PLACED_CODES.has(code) && !unknownOutcome;
2858
+ const orderExists = code === DUPLICATE_ORDER_CODE || message !== void 0 && /already have an active buy order/i.test(message);
2859
+ return new BuyOrderError(message ?? `createbuyorder ${stage} failed (HTTP ${res.statusCode})`, {
2860
+ stage,
2861
+ mayExist: !notPlaced,
2862
+ orderExists,
2863
+ ...code !== void 0 ? { code } : {},
2864
+ ...confirmationId !== void 0 ? { confirmationId } : {},
2865
+ body: body ?? res.body
2866
+ });
2867
+ }
2868
+ function safeJson(text) {
2869
+ if (!text) return void 0;
2870
+ try {
2871
+ return JSON.parse(text);
2872
+ } catch {
2873
+ return;
2874
+ }
2875
+ }
2876
+ function rawIdField(raw, field) {
2877
+ return raw.match(new RegExp(`"${field}"\\s*:\\s*"?(\\d+)"?`))?.[1] ?? null;
2878
+ }
2879
+ function sleep(ms, signal) {
2880
+ return new Promise((resolve, reject) => {
2881
+ if (signal?.aborted) {
2882
+ reject(signal.reason);
2883
+ return;
2884
+ }
2885
+ const timer = setTimeout(() => {
2886
+ signal?.removeEventListener("abort", onAbort);
2887
+ resolve();
2888
+ }, ms);
2889
+ const onAbort = () => {
2890
+ clearTimeout(timer);
2891
+ reject(signal?.reason);
2892
+ };
2893
+ signal?.addEventListener("abort", onAbort, { once: true });
2894
+ });
2370
2895
  }
2371
2896
  /**
2372
2897
  * Describes the message CEconItemPreviewDataBlock.
@@ -3474,6 +3999,7 @@ var SteamMobile = class extends EventEmitter {
3474
3999
  this.trade = new TradeNamespace(this.api, this.http, this.session, this.confirmations);
3475
4000
  this.community = new CommunityNamespace(this.http, this.session, this.confirmations, this.api);
3476
4001
  this.market = new MarketNamespace(this.http, this.session, this.confirmations, this.api);
4002
+ if (options.walletCurrency !== void 0) this.market.seedWalletCurrency(options.walletCurrency);
3477
4003
  this.session.on("refreshToken", (token) => this.emit("refreshToken", token));
3478
4004
  this.session.on("sessionExpired", (error) => this.emit("sessionExpired", error));
3479
4005
  this.session.on("debug", (message) => this.emit("debug", message));
@@ -3533,6 +4059,6 @@ var SteamMobile = class extends EventEmitter {
3533
4059
  }
3534
4060
  };
3535
4061
  //#endregion
3536
- export { ANDROID_PROFILE, AccessTokenError, AuthClient, CommunityNamespace, ConfirmationError, ConfirmationManager, CredentialSession, DEFAULT_CONTEXTID, DEFAULT_POLL_FULL_UPDATE_INTERVAL, DEFAULT_POLL_INTERVAL, DEFAULT_POLL_MAX_AGE_MS, DEFAULT_RATE_LIMIT_RETRY_MS, EAuthSessionGuardType, EAuthTokenPlatformType, EAuthTokenRevokeAction, EConfirmationMethod, EConfirmationType, EOfferFilter, EResult, ESessionPersistence, ETokenRenewalType, ETradeOfferState, ETradeStatus, EscrowError, FamilyViewError, HttpClient, HttpStatusError, IOS_PROFILE, ItemServerUnavailableError, LANG, LoginError, MarketBlockedError, MarketConfirmationLimitError, MarketNamespace, NewDeviceError, NoMobileAuthenticatorError, OfferLimitError, OpenIdError, Poller, PrivateInventoryError, ProxyError, RATE_LIMITS, RETRY_AFTER, RateLimitError, SessionManager, SteamError, SteamMobile, SteamSessionExpiredError, SteamWebApi, TERMINAL_AUTH_ERESULTS, TRANSIENT_ERESULTS, TargetCannotTradeError, ThrottledError, TradeBanError, TradeNamespace, TradeOffer, URLS, WalletBalanceLimitError, WebApiClient, confirmOpenid, decodeJwt, decodePreviewToken, encodePreviewToken, getPriceValueAsInt, getTradeHistory, getTradeOffersSummary, getTradeStatus, isTerminalAuthEResult, isTerminalState, isTransientEResult, loginWithCredentials, parseInventory, parseMarketHistory, parseOpenidForm, parsePartnerInventory, resolveMobileProfile, resolveTarget, secondsUntilExpiry, steamOpenidLogin };
4062
+ export { ANDROID_PROFILE, AccessTokenError, AuthClient, BuyOrderError, CommunityNamespace, ConfirmationError, ConfirmationManager, ConfirmationOutcomeUnknownError, CredentialSession, CurrencyMismatchError, DEFAULT_CONTEXTID, DEFAULT_MARKET_FEE_INFO, DEFAULT_POLL_FULL_UPDATE_INTERVAL, DEFAULT_POLL_INTERVAL, DEFAULT_POLL_MAX_AGE_MS, DEFAULT_RATE_LIMIT_RETRY_MS, EAuthSessionGuardType, EAuthTokenPlatformType, EAuthTokenRevokeAction, EConfirmationMethod, EConfirmationType, EOfferFilter, EResult, ESessionPersistence, ETokenRenewalType, ETradeOfferState, ETradeStatus, EscrowError, FamilyViewError, HttpClient, HttpStatusError, IOS_PROFILE, ItemServerUnavailableError, LANG, LoginError, MarketBlockedError, MarketConfirmationLimitError, MarketIneligibleError, MarketItemUnavailableError, MarketNamespace, NewDeviceError, NoMobileAuthenticatorError, OfferLimitError, OpenIdError, Poller, PrivateInventoryError, ProxyError, RATE_LIMITS, RETRY_AFTER, RateLimitError, SessionManager, SteamError, SteamMobile, SteamSessionExpiredError, SteamWebApi, TERMINAL_AUTH_ERESULTS, TRANSIENT_ERESULTS, TargetCannotTradeError, ThrottledError, TradeBanError, TradeNamespace, TradeOffer, URLS, WalletBalanceLimitError, WalletCurrencyUnknownError, WebApiClient, buyerPaysForSellerReceives, confirmOpenid, decodeJwt, decodePreviewToken, encodePreviewToken, feeInfoFromWalletInfo, feesForBuyerPays, getPriceValueAsInt, getTradeHistory, getTradeOffersSummary, getTradeStatus, isTerminalAuthEResult, isTerminalState, isTransientEResult, landingBuyerPays, loginWithCredentials, parseInventory, parseMarketHistory, parseOpenidForm, parsePartnerInventory, priceTextCurrency, resolveMobileProfile, resolveTarget, secondsUntilExpiry, sellerReceivesForBuyerPays, steamOpenidLogin, summarizeOrderbook };
3537
4063
 
3538
4064
  //# sourceMappingURL=index.mjs.map