Quick answer
Use Game.market.deal() only for one explicitly reviewed order ID. Fetch the current order again, verify it is the expected sell order and resource, cap the current price, limit the amount to order.amount, estimate transfer Energy, preserve Credit and Terminal Energy reserves, disable the request before calling the API, and verify the later transaction record instead of printing “bought” after OK.
The deal executor pays transfer Energy
The official market assigns transfer Energy and Terminal cooldown to the player who calls deal(). This remains true when buying from another player's sell order.
function estimateDealEnergy(
amount,
yourRoomName,
orderRoomName
) {
if (
!Number.isInteger(amount)
|| amount <= 0
|| typeof yourRoomName !== 'string'
|| typeof orderRoomName !== 'string'
) {
return null;
}
return Game.market.calcTransactionCost(
amount,
yourRoomName,
orderRoomName
);
}
The estimate uses amount and room names. Applied Terminal power effects can change actual transfer cost, so later Store and transaction records remain the settlement evidence.
Use amount, not remainingAmount
| Order field | Meaning | Deal use |
|---|---|---|
amount | Current amount available to deal | Hard execution ceiling |
remainingAmount | Remaining planned order capacity | Order lifecycle information |
totalAmount | Original or extended total capacity | Order history and maintenance |
An inactive order can have remaining capacity while its current amount is zero.
Refresh the exact order before execution
function getReviewedSellOrder(request) {
if (
!request
|| typeof request.orderId !== 'string'
) {
return {
ready: false,
reason: 'order-id-invalid',
order: null
};
}
const order = Game.market.getOrderById(
request.orderId
);
if (!order) {
return {
ready: false,
reason: 'order-unavailable',
order: null
};
}
if (order.type !== ORDER_SELL) {
return {
ready: false,
reason: 'order-not-sell',
order
};
}
if (order.resourceType !== request.resourceType) {
return {
ready: false,
reason: 'resource-mismatch',
order
};
}
return {
ready: true,
reason: 'order-reviewed',
order
};
}
Never execute a copied ID without rechecking its current direction, resource, price, room, and available amount.
Calculate Credit and Energy budgets
credit cost = order.price × amount
credits after = current credits - credit cost
terminal Energy after = current Energy - transaction Energy
For a normal resource purchase, the incoming resource is not included in the preflight Energy reserve. If the resource being purchased is Energy, this is deliberately conservative because the Terminal still needs enough Energy to execute the transaction before incoming Energy is settled.
Build a testable deal plan
function evaluateBuyDeal(input) {
const { request, order } = input;
if (!request || request.enabled !== true) {
return { ready: false, reason: 'disabled' };
}
if (!order) {
return { ready: false, reason: 'order-unavailable' };
}
if (
order.type !== ORDER_SELL
|| order.resourceType !== request.resourceType
) {
return { ready: false, reason: 'order-mismatch' };
}
if (
!Number.isInteger(request.amount)
|| request.amount <= 0
|| request.amount > order.amount
) {
return { ready: false, reason: 'amount-unavailable' };
}
if (
!Number.isFinite(request.maximumPrice)
|| order.price > request.maximumPrice
) {
return { ready: false, reason: 'price-above-limit' };
}
if (!Number.isFinite(input.transactionEnergy)) {
return { ready: false, reason: 'energy-cost-invalid' };
}
const creditCost = order.price * request.amount;
const creditReserve = Number.isFinite(
request.creditReserve
)
? request.creditReserve
: 0;
const energyReserve = Number.isFinite(
request.energyReserve
)
? request.energyReserve
: 0;
if (input.credits - creditCost < creditReserve) {
return {
ready: false,
reason: 'credit-reserve',
creditCost
};
}
if (
input.terminalEnergy - input.transactionEnergy
< energyReserve
) {
return {
ready: false,
reason: 'energy-reserve',
creditCost,
transactionEnergy: input.transactionEnergy
};
}
return {
ready: true,
reason: 'ready',
creditCost,
transactionEnergy: input.transactionEnergy,
creditsAfter: input.credits - creditCost,
terminalEnergyAfter:
input.terminalEnergy - input.transactionEnergy
};
}
Complete one-time buy example
State impact: this code may execute one real market deal. It writes a request snapshot and never retries automatically.
module.exports.loop = function () {
const request = Memory.market?.dealRequest;
if (!request || request.enabled !== true) {
return;
}
const terminal = typeof request.terminalId === 'string'
? Game.getObjectById(request.terminalId)
: null;
if (
!terminal
|| terminal.structureType !== STRUCTURE_TERMINAL
|| terminal.my !== true
|| terminal.isActive() !== true
|| terminal.cooldown > 0
) {
request.lastStatus = 'terminal-not-ready';
request.lastCheckedAt = Game.time;
return;
}
const reviewed = getReviewedSellOrder(request);
if (!reviewed.ready) {
request.lastStatus = reviewed.reason;
request.lastCheckedAt = Game.time;
return;
}
const order = reviewed.order;
const transactionEnergy =
Game.market.calcTransactionCost(
request.amount,
terminal.room.name,
order.roomName
);
const plan = evaluateBuyDeal({
request,
order,
credits: Game.market.credits,
terminalEnergy:
terminal.store.getUsedCapacity(RESOURCE_ENERGY),
transactionEnergy
});
request.lastStatus = plan.reason;
request.lastCheckedAt = Game.time;
if (!plan.ready) {
request.preview = {
currentPrice: order.price,
currentAmount: order.amount,
remainingAmount: order.remainingAmount,
creditCost: plan.creditCost ?? null,
transactionEnergy:
plan.transactionEnergy ?? transactionEnergy
};
return;
}
request.enabled = false;
request.status = 'submitted';
request.submittedAt = Game.time;
request.snapshot = {
orderId: order.id,
orderType: order.type,
resourceType: order.resourceType,
orderRoomName: order.roomName,
yourRoomName: terminal.room.name,
amount: request.amount,
price: order.price,
orderAmountBefore: order.amount,
remainingAmountBefore: order.remainingAmount,
creditsBefore: Game.market.credits,
terminalEnergyBefore:
terminal.store.getUsedCapacity(RESOURCE_ENERGY),
creditCost: plan.creditCost,
transactionEnergy: plan.transactionEnergy
};
const result = Game.market.deal(
order.id,
request.amount,
terminal.room.name
);
request.result = result;
request.resultAt = Game.time;
request.status = result === OK
? 'accepted-pending-settlement'
: 'failed-review-required';
console.log(JSON.stringify({
type: 'market-deal-result',
orderId: order.id,
roomName: terminal.room.name,
resourceType: order.resourceType,
amount: request.amount,
price: order.price,
creditCost: plan.creditCost,
transactionEnergy: plan.transactionEnergy,
result
}));
};
Disable before calling deal
Market conditions can change between ticks. A failed call must not repeat automatically with an old order snapshot, old price ceiling, or old budget. Re-enable only after reviewing the current order and request.
The market can change after preflight
Another player may consume the order, and the official documentation says the closer player takes precedence when multiple players try the same deal. Your Terminal or Credits can also change because of another module. Preflight reduces mistakes but cannot reserve the order. The API return code remains authoritative.
Centralize the 10-deal account limit
function reserveMarketDealSlot() {
Memory.market ??= {};
if (Memory.market.dealSlotTick !== Game.time) {
Memory.market.dealSlotTick = Game.time;
Memory.market.dealSlotsUsed = 0;
}
if (Memory.market.dealSlotsUsed >= 10) {
return false;
}
Memory.market.dealSlotsUsed += 1;
return true;
}
This local counter coordinates your modules, but only the actual API result proves whether the account-wide limit is available. Reserve the slot immediately before the call and do not run independent deal loops.
Verify settlement on a later tick
function findMatchingIncomingTransaction(snapshot) {
return Game.market.incomingTransactions.find(tx =>
tx.time >= snapshot.submittedAt
&& tx.order?.id === snapshot.orderId
&& tx.resourceType === snapshot.resourceType
&& tx.amount === snapshot.amount
&& tx.to === snapshot.yourRoomName
) || null;
}
Also compare Credits, Terminal Store, Energy, order amount, and the saved snapshot. Transaction arrays are finite recent records, so verify promptly and avoid treating a missing old record as definitive proof of failure.
Handle return codes
| Code | Interpretation | Review |
|---|---|---|
OK | Deal scheduled | Transactions, Credits and Store later |
ERR_NOT_OWNER | No usable Terminal in the selected room | Room and Terminal ownership |
ERR_NOT_ENOUGH_RESOURCES | Credits, resource, or transaction Energy insufficient | Direction-specific budgets |
ERR_FULL | More than 10 deals attempted this tick | Global market scheduler |
ERR_INVALID_ARGS | Order, amount, or room invalid | Fresh order snapshot |
ERR_TIRED | Terminal cooling down | terminal.cooldown |
Debugging checklist
- Use one reviewed order ID.
- Fetch the order again immediately before execution.
- Verify direction and resource type.
- Use
order.amountas the current ceiling. - Enforce a maximum price.
- Calculate Credit cost and reserve.
- Calculate Terminal Energy cost and reserve.
- Check Terminal ownership, activity, and cooldown.
- Coordinate the 10-deal account limit.
- Disable before calling.
- Save snapshot and return code.
- Verify transactions and balances later.
Scope and next steps
This guide does not automatically select orders, predict prices, split deals, sell to buy orders, support account-bound resources, optimize multiple Terminals, or retry after market races. Continue with direct Terminal transfers.
Frequently asked questions
Is the cheapest order always best?
No. Transfer Energy, distance, amount, urgency, reserves, and current availability also matter.
Why not use remainingAmount?
It can include capacity that is not currently active or available.
Can OK still require later verification?
Yes. OK schedules the operation; settlement evidence appears in later state.
Should an error retry automatically?
No. Refresh the order and require a new review.