Skip to main content

Trade response

The trade response bot accepts or declines one offer from the trade inbox. It sends only when every lock is open, and it never sends twice for one offer.

This answers real offers and can't be undone

Accepting moves players on real rosters. The worker refuses unless a person approved the exact action on the exact offer as ESPN shows it now.

What starts it?​

The core API publishes trade_responses when a person approves an accept or decline in the inbox. You can also run node src/bots/trade_response.js '<json>'.

What does the message contain?​

FieldRequiredNotes
espn_transaction_idYesThe offer's ESPN id.
actionYesaccept or decline.
approvalTo send{decision_key, approved_by, reviewed_note}. The key is 64 hex characters.
commentNoA note sent with the response. Part of the key.
retryNo{of_claim_id, approved_by, verified_not_answered}. Needs a new tracking_id.
tracking_idNo

reviewed_note is the exact proposer note the person reviewed, a string or null. It must match ESPN's current note, including whitespace. An empty string differs from null.

What are the locks?​

LockOpen when
DRY_RUNExplicitly false.
TRADE_RESPONSE_ENABLEDtrue, 1 or yes. Separate from TRADE_SUBMIT_ENABLED.
Approvaldecision_key equals the key recomputed from a fresh read.
Verified actionThe action is in VERIFIED_TRADE_RESPONSE_ACTIONS in espn_api.js, which starts empty.
Verified shapePENDING_TRADE_SHAPE_VERIFIED is true.

The decision_key is a SHA-256 over league, season, team, transaction id, action, offer fingerprint and note. With any lock closed the bot records response_blocked and the body it would have sent.

When does it always refuse?​

  • The offer is gone, or expires within 5 minutes.
  • The offer isn't open in the inbox, or changed since it was read.
  • Another response to the offer is claimed, in progress, settled, unknown or rejected.
  • For an accept: the roster is locked, the offer has no current score, or the trade puts us over the roster limit.

How does it avoid answering twice?​

  • Reconcile first. An existing claim on the key settles from ESPN reads. A responded key returns duplicate. A responding or unknown one is read back and never resent.
  • One claim per offer. The claim row and the entry's responding status are written in one transaction under the trade-inbox:{league}:{transactionId} lock. An accept and a decline can't both go out.
  • Read back. After the POST, and after an unknown outcome, ESPN is read again. Only a confirming read settles it. Anything else stops for a manual check in ESPN.

How do I retry a stuck response?​

A response ESPN rejected, or one with an unknown outcome, stays blocked until a person retries it. Send the same approval with a new tracking_id and retry.

Previous stateRetry needs
Rejectedretry.of_claim_id and approved_by.
UnknownAlso verified_not_answered: true, and a fresh read that still shows the offer pending with no related history row.

If the reads now confirm the earlier response, it is settled instead. The retried row becomes superseded in the same transaction as the new claim.

What can go wrong?​

FailureResult
Missing or wrong fields, mismatched key or note, offer gone or too close to expiryPermanentError
ESPN rejects the sessionPermanentError. Refresh the session and retry from the inbox.
ESPN returns 429 or 5xxOutcome unknown. The claim stays blocked for a human retry.