fifa17-recon: transfer market sell/list flow (stateful, tested)

Complete the market loop (browse + buy + sell). POST auctionhouse (FutISStart)
lists an owned club item -> profile.listings + returns {id:tradeId}. tradePile
builds a validated auction record per listing from the owned item + prices
(freeze-safe, same 0x18013e410 shape). DELETE trade/{id} removes the listing.
fut_store gains list_for_sale/listings/remove_listing (tradeId space 900500000+).

test_market_buy.py extended with sell/delist checks (temp profile, no real-save
mutation): list -> tradePile shows it with prices -> delist empties it. All pass.
Read-only contract suite still 311/311. Functional (FIFA's exact sell params)
pending live test; freeze-safe by construction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
This commit is contained in:
funman300
2026-08-02 20:21:10 -07:00
parent f38231d89d
commit 0081dfc8d4
3 changed files with 77 additions and 4 deletions
+36 -3
View File
@@ -190,7 +190,7 @@ ROUTES = [
(re.compile(G + r"/watchList"), lambda m, h: watchlist_route(h)),
(re.compile(G + r"/auctionhouse"), lambda m, h: auctionhouse_route(h)),
(re.compile(G + r"/marketdata"), lambda m, h: (200, {"minPrice": 150, "maxPrice": 15000})),
(re.compile(r"/ut/delete/game/[^/]+/trade"), lambda m, h: (200, {})),
(re.compile(r"/ut/delete/game/[^/]+/trade"), lambda m, h: delete_trade_route(h)),
(re.compile(r"/ut/delete/game/[^/]+/watchList"), lambda m, h: (200, {})),
(re.compile(G + r"/club"), lambda m, h: (200, {"itemData": STORE.items()})),
]
@@ -383,6 +383,16 @@ def auctionhouse_route(h):
# POST = FutISStart (list item for sale) -> {"id": new tradeId}
# PUT = .../relist (relist all expired) -> ack {}
if h.command == "POST":
# FutISStart: list an owned club item for sale -> {"id": tradeId}
try:
b = json.loads(h._body.decode()) if getattr(h, "_body", b"") else {}
except Exception:
b = {}
item_id = (b.get("itemData") or {}).get("id") or b.get("itemId")
if item_id:
tid = STORE.list_for_sale(item_id, b.get("startingBid", 150), b.get("buyNowPrice", 0))
log(" MARKET: listed item %s -> tradeId %d" % (item_id, tid))
return 200, {"id": tid}
return 200, {"id": STORE.new_item_id()}
if h.command == "PUT":
return 200, {}
@@ -427,8 +437,31 @@ def trade_route(h):
def tradepile_route(h):
# The user's OWN sale pile -- empty until they list something (no live sell flow yet).
return 200, {"auctionInfo": [], "credits": STORE.coins(), "total": 0}
# The user's OWN sale pile: build a validated auction record per active listing
# from the owned club item + its list prices. Freeze-safe (same record shape).
by_id = {it["id"]: it for it in STORE.items()}
seller = STORE.profile().get("personaName", "OpenFUT")
recs = []
for l in STORE.listings():
it = by_id.get(l["itemId"])
if not it:
continue
card = dict(it); card["itemState"] = "listFS"
recs.append({
"tradeId": l["tradeId"], "itemData": card, "tradeState": "active",
"buyNowPrice": l.get("buyNowPrice", 0), "startingBid": l.get("startingBid", 150),
"currentBid": 0, "bidState": "none", "expires": 3600,
"sellerName": seller, "sellerEstablished": 1, "watched": False, "coinsProcessed": 0,
})
return 200, {"auctionInfo": recs, "credits": STORE.coins(), "total": len(recs)}
def delete_trade_route(h):
# DELETE ut/delete/game/fifa17/trade/{id} -- remove a listing from the sale pile.
m = re.search(r"/trade/(\d+)", h.path)
if m:
STORE.remove_listing(int(m.group(1)))
return 200, {}
def watchlist_route(h):