@assetpay/steam-mobile 1.0.5 → 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/README.md CHANGED
@@ -39,6 +39,7 @@ persistent CM connection.
39
39
  - [bot.trade](#bottrade) — sending, reading, and polling trade offers
40
40
  - [TradeOffer](#tradeoffer) — a single offer
41
41
  - [bot.community](#botcommunity) — inventories, profiles, trade URLs, API keys
42
+ - [bot.market](#botmarket) — Community Market: listings, buy orders, history, order book, fees
42
43
  - [bot.session](#botsession) — token lifecycle and sessions
43
44
  - [bot.confirmations](#botconfirmations) — low-level mobile confirmations
44
45
  - [decodePreviewToken](#decodepreviewtoken) — decode CS2 item inspect/certificate data
@@ -174,6 +175,7 @@ The root client. It owns the HTTP layer, the session, and the namespaces, and it
174
175
  - `proxy` — Optional. A proxy URL (`http://user:pass@host:port`, `socks5://…`, etc.). All traffic — API, community, and confirmations — routes through it.
175
176
  - `mobileProfile` — Optional. `"ios"` (default), `"android"`, or a `Partial<MobileProfile>` to override individual fields. See [Mobile-app impersonation](#mobile-app-impersonation).
176
177
  - `polling` — Optional. `true` to start polling with default cadence after `login()`, or a [`PollOptions`](#polloptions) object to tune it / resume from saved `pollData`.
178
+ - `walletCurrency` — Optional. The account's known wallet currency id (`1` USD, `3` EUR, …), e.g. persisted from an earlier `getWalletDetails()`. Market calls check against it from the first request; see [bot.market](#botmarket).
177
179
 
178
180
  Throws `SteamSessionExpiredError` synchronously if the refresh token is malformed (no SteamID in its
179
181
  payload). Does **not** make any network request.
@@ -683,6 +685,13 @@ state, VAC status, privacy state, and the `isLimited` flag. (Steam level isn't i
683
685
  Returns the account's Steam level as a `Promise<number>` via `IPlayerService/GetSteamLevel` (which
684
686
  accepts the access token — no API key required).
685
687
 
688
+ ### getTwoFactorStatus()
689
+
690
+ Returns the mobile-authenticator status via `ITwoFactorService/QueryStatus` as
691
+ `Promise<RawTwoFactorStatus>`: `state`, `email_validated`, `time_created` (when the authenticator was
692
+ added), and the rest of Steam's fields. For Steam's can-trade verdict (Steam Guard / new-device holds
693
+ included) use `getWebTradeEligibility()`.
694
+
686
695
  ### community.ensureApiKey(\[domain])
687
696
 
688
697
  - `domain` — Optional hostname for the key (default `"assetpay.gg"`).
@@ -699,6 +708,217 @@ for completeness. Returns `Promise<void>`.
699
708
 
700
709
  ---
701
710
 
711
+ ## bot.market
712
+
713
+ The Steam Community Market. Needs an account that can use the market (not limited). Responses are
714
+ returned as Steam sends them (snake_case, unknown fields preserved); nothing is retried — rate limits
715
+ surface as `RateLimitError` (HTTP 429), and the order book's 403 wall as `ThrottledError`. Every
716
+ method takes optional controls on its options object: `signal` (an `AbortSignal` — use
717
+ `AbortSignal.timeout(ms)` for a deadline on the whole call) and `timeoutMs` (per HTTP request; the
718
+ default is 50s). Writes are never re-sent implicitly, not even after Steam's eligibility check.
719
+
720
+ **Currency — fails closed.** `bot.market.walletCurrency` (`1` USD, `3` EUR, …) is seeded through
721
+ `new SteamMobile({ walletCurrency })` / `bot.market.seedWalletCurrency()`, or learned by
722
+ [`getWalletDetails()`](#getwalletdetails) from a wallet that exists. Once known it can only be
723
+ confirmed: a response in another currency throws `CurrencyMismatchError` instead of switching.
724
+ `bot.market.walletCurrencyVerified` turns true once a live read in this session answered in it
725
+ (the wallet, an order book, your buy orders or listings) — writes require it, so a wrong seed can't
726
+ price a write. Every currency-dependent call checks it:
727
+
728
+ - `getMyListings`, `getMyHistory`, `getOrderbook`, `getPriceHistory` throw
729
+ `WalletCurrencyUnknownError` when no currency is known, and `CurrencyMismatchError` when a response
730
+ is in another currency (order book `eCurrency`, buy orders' `wallet_currency`, listings'
731
+ `currencyid`, the price-history `price_prefix` / `price_suffix`). The rendered history is the
732
+ exception: a row priced in another currency (an account that changed currency keeps its old rows)
733
+ is moved to `malformed` with reason `other_currency`, never returned as money, and the rest of the
734
+ page is returned. Passing `null` as the currency option skips the check for that call. History and
735
+ price-history formats are pinned for USD and EUR only.
736
+ - `sellItem` and `createBuyOrder` need the wallet currency known and verified, and refuse a
737
+ `currency` option that differs from it.
738
+
739
+ ### getWalletDetails(\[options])
740
+
741
+ Returns the wallet via `IUserAccountService/GetClientWalletDetails` as `Promise<RawWalletDetails>`:
742
+ `has_wallet`, `balance` / `delayed_balance` (int64 cents, as strings), `currency_code` (the wallet
743
+ currency id — `1` USD, `3` EUR, …), `user_country_code`, `time_most_recent_txn`,
744
+ `formatted_balance`, and the rest of Steam's fields. Learns `walletCurrency`.
745
+
746
+ ### getMarketWalletInfo(\[options])
747
+
748
+ Returns `g_rgWalletInfo` from the market page as `Promise<RawMarketWalletInfo>`: the fee parameters
749
+ (`wallet_fee`, `wallet_fee_percent`, `wallet_fee_minimum`, `wallet_fee_base`,
750
+ `wallet_publisher_fee_percent_default`) and caps (`wallet_max_balance`, `wallet_trade_max_balance`)
751
+ in the wallet currency. Throws `SteamSessionExpiredError` unless the page was rendered for this
752
+ account (a logged-out page carries a default USD wallet). It never teaches the currency; a known
753
+ one must match. `feeInfoFromWalletInfo(info)` turns it into a validated `MarketFeeInfo`.
754
+
755
+ ### getMyListings(\[options])
756
+
757
+ - `options`
758
+ - `currency` — Optional. Wallet currency id, sent as `currency` (default `walletCurrency`; `null`
759
+ sends none and skips the checks).
760
+
761
+ Loads every page of your listings. Returns `Promise<MyListings>`:
762
+ `{ listings, listings_to_confirm, listings_on_hold, buy_orders, num_active_listings, complete }` with
763
+ Steam's raw entries, listings deduplicated by `listingid`. Every listing must carry its `currencyid`
764
+ and every buy order its `wallet_currency`. `complete` is `false` when the pages did not add up to
765
+ `num_active_listings`, the total moved between pages, or the first page had no `buy_orders` array —
766
+ never read "absent from an incomplete result" as sold, cancelled or filled. A
767
+ listing's `price` is what you receive and `fee` Steam's cut (the buyer pays `price + fee`).
768
+ `listings_to_confirm` are listings still awaiting mobile confirmation; their item can't be relisted
769
+ until the listing is confirmed or removed with [`cancelListing`](#cancellistinglistingid-options).
770
+
771
+ ### getMyHistory(\[options])
772
+
773
+ - `options`
774
+ - `count` — Optional. Rows per page (default `500`).
775
+ - `start` — Optional. Row offset, for paging back.
776
+ - `currency` — Optional. Currency a priced row must be in to be returned as money (default
777
+ `walletCurrency`; `null` skips). Rows in any other currency go to `malformed` as `other_currency`.
778
+
779
+ Parses the rendered market history into `Promise<MarketHistory>`:
780
+ `{ events, sales, purchases, listingEvents, malformed, totalCount }` — `events` is every row in page
781
+ order (newest first), the others split it by `type`. Every event has `historyId`
782
+ (`history_row_<listingid>_<eventid>`), `listingid`, `eventid`, `type`, `itemName`, `gameName`, `actedOn` /
783
+ `listedOn` (display dates, no year), `displayPrice` and `priceInCents` (also as `receivedAmount` on
784
+ sales and `paidAmount` on purchases). Sales and purchases also carry the asset: `appID`,
785
+ `contextID`, `assetID`, `classID`, `instanceID`, `marketName`, `unOwnedContextID`, `unOwnedID`.
786
+ A sale or purchase whose price is missing or parses to 0 cents is kept out of every list and returned
787
+ in `malformed` (`historyId`, `displayPrice`, `gainOrLoss`, `reason`: `"zero_price"` |
788
+ `"missing_price"` | `"other_currency"`) — record it, never book it.
789
+ `parseMarketHistory(body)` and `priceTextCurrency(text)` are exported for raw responses.
790
+
791
+ ### getOrderbook(appid, marketHashName\[, options])
792
+
793
+ - `options`
794
+ - `expectedCurrency` — Optional (default `walletCurrency`; `null` skips). Throws
795
+ `CurrencyMismatchError` instead of returning a book in any other currency.
796
+
797
+ Returns the live order book as `Promise<RawOrderbookData>`: `eCurrency`, `amtMinSellOrder` /
798
+ `amtMaxBuyOrder` (best ask / bid in cents, `null` — not `NaN` — when that side is empty), and
799
+ `rgCompactSellOrders` / `rgCompactBuyOrders` (flat `[price, qty, price, qty, …]`, best first). The
800
+ endpoint always answers in the wallet currency. A 403 here is Steam's order-book throttle wall and
801
+ throws `ThrottledError`. `summarizeOrderbook(data)` reads the best levels —
802
+ `{ currency, lowestAsk, askVolumeAtLowest, secondLowestAsk, highestBid, bidVolumeAtHighest,
803
+ secondHighestBid }`, an empty side as `null` (never a 0-cent price) — fail-closed: every value must be
804
+ a whole number, levels strictly ordered with positive prices and quantities, and the best level must
805
+ match the depth; anything else throws.
806
+
807
+ ### getPriceHistory(appid, marketHashName\[, options])
808
+
809
+ - `options`
810
+ - `expectedCurrency` — Optional (default `walletCurrency`; `null` skips).
811
+
812
+ Returns the price history rows as `Promise<RawPriceHistoryPoint[]>` —
813
+ `[date, price (major units), volume]`; `[]` for an item without sales history. Throws when Steam
814
+ answers `success: false`, and `CurrencyMismatchError` when the response's currency markers don't
815
+ match.
816
+
817
+ ### getMarketItemDetails(appid, marketHashName\[, options])
818
+
819
+ Searches the market and returns the row whose `hash_name` matches exactly (`sell_listings`,
820
+ `asset_description`), or `null`. A `success: false` answer throws, so a throttled reply is never read
821
+ as "not on the market".
822
+
823
+ ### sellItem(options)
824
+
825
+ - `options`
826
+ - `appid`, `assetid`
827
+ - `price` — What **you** receive, in cents (the buyer pays this plus fees). Positive integer.
828
+ - `contextid` — Optional (default `"2"`).
829
+ - `amount` — Optional (default `1`).
830
+ - `currency` — Optional. The currency `price` is in; must equal `walletCurrency`.
831
+
832
+ Lists an item and returns Steam's response as `Promise<RawSellItemResponse>`. The listing still needs
833
+ its mobile confirmation — see [`confirmListingIds`](#confirmlistingidslistingids-options). Refusals
834
+ throw `MarketConfirmationLimitError` (with `reason`: `"too_many_pending"` | `"item_pending"` |
835
+ `"unknown"`), `WalletBalanceLimitError`, `MarketBlockedError`, `MarketItemUnavailableError`, otherwise
836
+ a `SteamError` with Steam's message. A transport failure leaves the outcome unknown: read
837
+ `getMyListings` before listing the asset again.
838
+
839
+ ### cancelListing(listingId\[, options])
840
+
841
+ Removes a listing. Returns `Promise<void>`.
842
+
843
+ ### createBuyOrder(options)
844
+
845
+ - `options`
846
+ - `appid`, `marketHashName`, `quantity`
847
+ - `priceTotal` — Price per unit × quantity, in cents (must divide by `quantity`).
848
+ - `currency` — Optional. Must equal `walletCurrency`, which must be known.
849
+
850
+ Places a buy order and returns its `buy_orderid` as `Promise<string>` (64-bit ids are read from the
851
+ raw response, never through a JS number). When Steam answers with a mobile confirmation (HTTP 406),
852
+ exactly that confirmation is accepted (needs `identitySecret`) and the order finalized. No step is
853
+ ever retried. Errors:
854
+
855
+ - `RateLimitError`, `SteamSessionExpiredError`, `MarketIneligibleError` from the first request —
856
+ nothing was placed.
857
+ - `MarketBlockedError` (Steam code 15 or "unable to use the Community Market", any step) — refused;
858
+ account-level.
859
+ - `BuyOrderError` for everything else: `stage` (`"create"` | `"confirm"` | `"finalize"`), `code`
860
+ (Steam's `success` code), `confirmationId`, **`orderExists`** (Steam says an order for this item
861
+ already exists — 29 — possibly at another price) and **`mayExist`** — `true` unless Steam refused
862
+ with a code proven to mean "not placed" (25 order limit, 107 insufficient funds) and nothing in its
863
+ message says the outcome is unknown ("trouble hearing back… double check…"). Read `getMyListings`
864
+ before acting again whenever it is `true`.
865
+
866
+ Only a confirmation of type `BuyOrder` whose creator is the order's `confirmation_id` is ever accepted.
867
+
868
+ ### cancelBuyOrder(buyOrderId\[, options])
869
+
870
+ Cancels a buy order. Returns Steam's response as `Promise<RawCancelBuyOrderResponse>` when it reports
871
+ success; any other verdict throws a `SteamError` with Steam's code as `eresult`.
872
+
873
+ ### confirmListingIds(listingIds\[, options])
874
+
875
+ Accepts exactly the pending market-listing confirmations created by those listing ids (a listing
876
+ confirmation's `creator` is its listing id), in one request. Returns
877
+ `Promise<{ confirmed, missing }>` — `missing` lists ids with no pending confirmation. Nothing else is
878
+ ever confirmed.
879
+
880
+ ### confirmListings(expected\[, options])
881
+
882
+ - `expected` — `{ [market_hash_name]: number of listings created }`.
883
+
884
+ Name-based fallback: accepts the pending market-listing confirmations that match, in one request:
885
+ oldest first, exact name first, then a substring match (the confirmation text can omit the wear; an
886
+ empty one never matches). It cannot tell two listings of one name apart and never checks the price —
887
+ prefer `confirmListingIds`. Returns `Promise<{ confirmed, skipped }>`
888
+ ([`Confirmation`](#confirmation)`[]` each); unmatched ones stay pending in `skipped`.
889
+
890
+ ### rejectListings(\[types, options])
891
+
892
+ - `types` — Optional [`EConfirmationType`](#econfirmationtype)`[]` (default `[MarketListing]`; pass
893
+ `[BuyOrder]` for buy orders).
894
+ - `options.keepCreators` — Optional. Creator ids (listing ids, buy-order confirmation ids) to leave
895
+ pending — work still in flight.
896
+
897
+ Rejects every other pending confirmation of those types in one request and returns them. Other types
898
+ are never touched.
899
+
900
+ ### Fees
901
+
902
+ Steam's own fee math (its client's `CalculateFeeAmount` /
903
+ `CalculateAmountToSendForDesiredReceivedAmount`), exact to the cent:
904
+
905
+ - `sellerReceivesForBuyerPays(buyerPays, feeInfo)` — what a seller receives at a listed price.
906
+ - `buyerPaysForSellerReceives(sellerReceives, feeInfo)` — `{ amount, steamFee, publisherFee, fees }`.
907
+ - `feesForBuyerPays(buyerPays, feeInfo)` — the fee split of a listed price.
908
+ - `landingBuyerPays(targetBuyerPays, feeInfo)` — the price a listing actually shows when listed to
909
+ receive `sellerReceivesForBuyerPays(target)`: not every price is reachable, so it can sit a cent or
910
+ two below the target; `0` when the target pays out nothing (1c, 2c).
911
+ - `feeInfoFromWalletInfo(g_rgWalletInfo[, publisherFeePercent])` — a validated `MarketFeeInfo`, or
912
+ `null` when a field is missing or out of range.
913
+
914
+ `feeInfo` is a `MarketFeeInfo` (`walletFee`, `walletFeePercent`, `walletFeeMinimum`, `walletFeeBase`,
915
+ `publisherFeePercent`, all fractions — `0.05`, never `5`); build it from
916
+ [`getMarketWalletInfo()`](#getmarketwalletinfo), or use `DEFAULT_MARKET_FEE_INFO` (5% + 10%, min 1c
917
+ each — CS2 and Rust as of 2026-10). Non-integer or negative cents, and fee info out of range, throw
918
+ `RangeError`.
919
+
920
+ ---
921
+
702
922
  ## bot.session
703
923
 
704
924
  The `SessionManager` owns the token lifecycle. You rarely call it directly — the namespaces refresh the
@@ -757,6 +977,12 @@ Accept or reject (cancel) a single confirmation by its `id` and `nonce` (the `ke
757
977
  [`getPending`](#getpending)). Each returns `Promise<void>` and handles the time-offset + per-request
758
978
  HMAC timestamp for you.
759
979
 
980
+ ### acceptConfirmations(confs) · rejectConfirmations(confs)
981
+
982
+ - `confs` — The confirmations to act on (`id` + `key`, e.g. from [`getPending`](#getpending)).
983
+
984
+ Accept or reject a selected set in one request. Returns `Promise<void>`; an empty set is a no-op.
985
+
760
986
  ### acceptAll()
761
987
 
762
988
  Accepts every pending confirmation and resolves to the [`Confirmation`](#confirmation)`[]` it acted on.
@@ -776,13 +1002,13 @@ Returns the outstanding confirmations as `Promise<`[`Confirmation`](#confirmatio
776
1002
 
777
1003
  ### respondToConfirmation(confID, confKey, time, key, accept)
778
1004
 
779
- - `confID` — The confirmation id.
780
- - `confKey` — The confirmation's nonce.
1005
+ - `confID` — The confirmation id, or an array of ids.
1006
+ - `confKey` — The confirmation's nonce, or an array of nonces (same order as `confID`).
781
1007
  - `time` — A Unix timestamp.
782
1008
  - `key` — A confirmation key, or `{ tag, key }`.
783
1009
  - `accept` — `true` to allow, `false` to cancel.
784
1010
 
785
- Accepts or rejects a single confirmation. Returns `Promise<void>`.
1011
+ Accepts or rejects the confirmation(s) in one request. Returns `Promise<void>`.
786
1012
 
787
1013
  ### acceptConfirmationForObject(objectID)
788
1014
 
@@ -1106,7 +1332,7 @@ All are exported and have the standard Steam numeric values.
1106
1332
  ### EConfirmationType
1107
1333
 
1108
1334
  `Invalid (0)`, `Generic (1)`, `Trade (2)`, `MarketListing (3)`, `FeatureOptOut (4)`,
1109
- `PhoneNumberChange (5)`, `AccountRecovery (6)`.
1335
+ `PhoneNumberChange (5)`, `AccountRecovery (6)`, `BuyOrder (12)`.
1110
1336
 
1111
1337
  ### EOfferFilter
1112
1338
 
@@ -1145,6 +1371,7 @@ one shared classifier so the same `instanceof` check works everywhere.
1145
1371
  | `HttpStatusError` | `statusCode` | A non-2xx HTTP response. |
1146
1372
  | `SteamSessionExpiredError` | — | The session/token is no longer valid; re-authenticate. |
1147
1373
  | `RateLimitError` | `statusCode?`, `retryAfterMs`, `unlockAt` | Rate limited (HTTP 429 or eresult 84). `unlockAt` is a millisecond epoch when you may retry — **always populated** (a conservative default when Steam gives no hint). |
1374
+ | `ThrottledError` | `statusCode = 403` | The order book's 403 throttle wall. Not a `RateLimitError`: it persists and switching proxy doesn't help — stop using the account for a while. |
1148
1375
  | `ProxyError` | `cause?` | A request through a configured proxy failed at the transport layer (unreachable / refused / timeout / auth). Only thrown when a `proxy` is set. |
1149
1376
  | `EscrowError` | `escrowDays` | The trade would be (or is) held in escrow. |
1150
1377
  | `TradeBanError` | — | The account is trade-banned. |
@@ -1155,6 +1382,15 @@ one shared classifier so the same `instanceof` check works everywhere.
1155
1382
  | `PrivateInventoryError` | — | Partner's inventory is private (or friends-only and we're not friends). A trade URL/token does **not** bypass inventory privacy. |
1156
1383
  | `ConfirmationError` | — | A mobile-confirmation step failed (incl. missing `identitySecret` when calling [`offer.confirm()`](#confirm)). |
1157
1384
  | `FamilyViewError` | — | Family View is restricting the account. |
1385
+ | `MarketConfirmationLimitError` | `eresult?`, `reason` | `sellItem` refused over pending confirmations: `too_many_pending` (confirm some first) or `item_pending` (this item already has one pending). |
1386
+ | `WalletBalanceLimitError` | `eresult?` | `sellItem` would push the wallet past Steam's maximum balance. |
1387
+ | `MarketBlockedError` | `eresult?` | The account is currently unable to use the Community Market (sell or buy). |
1388
+ | `MarketItemUnavailableError` | `eresult?` | `sellItem` refused: the asset is no longer in the inventory or is not marketable. |
1389
+ | `MarketIneligibleError` | — | Steam keeps redirecting to its market eligibility check: the account can't use the market (limited, new device, …). |
1390
+ | `CurrencyMismatchError` | `expected`, `actual` | A market response or a caller's price is in another currency than required; nothing was compared. |
1391
+ | `WalletCurrencyUnknownError` | — | A currency-dependent market call ran with no known wallet currency. |
1392
+ | `BuyOrderError` | `stage`, `code?`, `mayExist`, `orderExists`, `confirmationId?` | `createBuyOrder` failed at a step; `mayExist` says the order may have been placed anyway, `orderExists` that one exists for the item. |
1393
+ | `ConfirmationOutcomeUnknownError` | (extends `ConfirmationError`) | Acting on a confirmation got an unreadable answer: it may or may not have been accepted. |
1158
1394
  | `LoginError` | `extendedErrorMessage?`, `isTransient` | Credential-login failure; `isTransient` is `true` for a retryable blip (timeout / service unavailable) rather than bad credentials. |
1159
1395
  | `NoMobileAuthenticatorError` | (extends `LoginError`) | `sharedSecret` was supplied but the account has no mobile authenticator attached, so TOTP can't answer the guard challenge. Use `steamGuardCode` / `onSteamGuardRequired` with an email code instead. |
1160
1396
 
@@ -1233,4 +1469,6 @@ pnpm smoke # read-only health check across the whole API
1233
1469
  pnpm watch # live trade-event watcher — send the bot a trade and watch it fire
1234
1470
  pnpm trade # send → confirm → cancel lifecycle (gated: SEND=1 PARTNER_TRADE_URL=…; needs a non-limited bot)
1235
1471
  pnpm partner-inventory # load a partner's inventory via /partnerinventory/ (PARTNER_TRADE_URL=…; surfaces PrivateInventoryError etc.)
1472
+ pnpm market-probe # read-only market + account-status probe (PROBE_APPID / PROBE_ITEM; needs a non-limited account)
1473
+ pnpm market-trade # market write round trip → debug/out report (gated: LIVE_WRITES=1 PROBE_ASSETID=…; lists far above market, 3c buy order, cleans up)
1236
1474
  ```