Files
max bittkerandClaude Opus 5 9958f2b074 fix(sdk): correct interface-option clicks and make quantity actions honest
Audit of codex/porcelain-action-reliability, landing the changes that fix
real defects and leaving the speculative parts behind.

Interface options are published with a 1-based `index` label, but
`sendClickInterfaceOption` takes a 0-based array position. `fletchLogs`
and `craftLeather` matched an option by text and then fed its `index`
straight into that method, so both crafted the product *after* the one
requested — and matching the first option skipped it entirely. Adds
`sdk.clickInterfaceOption(selector)`, which resolves by object or visible
text and dispatches the option's `componentId`, and uses it at both call
sites. `sendClickInterfaceOption` keeps its behaviour with a renamed
parameter and a doc comment naming the trap.

`buyFromShop`/`sellToShop` decomposed the requested amount into an array
of 10/5/1 steps before sending anything, so `buyFromShop(item, 1e9)`
allocated a hundred-million-element array. Quantities are now validated
up front and the packets are streamed under a packet cap and deadline;
`sellAllToShop`'s unbounded `while (true)` is bounded the same way.

`sellToShop` reused the shop slot captured before the loop. Selling a
batch of non-stackables removes that slot, so later packets sold whatever
had slid into it. The slot is re-resolved by item id before each packet.

Shop and bank quantity actions returned `success: true` for short fills,
so a caller that asked for 28 proceeded believing it had 28. They now
report `requestedAmount`, the amount that actually moved, and
`partial: true`, and reserve `success` for an exact fill.

`chopTree` returned success when the tree merely despawned — which
happens when another player fells it — and then reported logs already in
the inventory as the ones just chopped. Success now requires the log
count to rise; the despawn still ends the wait, as a failure.

`withdrawItem` identified the withdrawn item by diffing inventory slots,
which names whatever landed in a changed slot rather than what was
withdrawn. It now tracks the bank item's id.

Also regenerates sdk/API.md, which was already stale on main (`bun run
check` fails there at docs:api:check), and adds unit coverage for option
resolution and quantity handling to test:focused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 18:16:14 -04:00

3.6 KiB

Banking

Successful patterns for bank interactions.

Opening the Bank

// Open bank (finds banker NPC or booth automatically)
const openResult = await bot.openBank();
if (!openResult.success) {
    console.log(`Failed to open bank: ${openResult.message}`);
}

// Deposit item by name
const depositCoins = await bot.depositItem(/coins/i);  // deposits all
const depositSword = await bot.depositItem(/sword/i, 1);  // deposits 1

// Withdraw item by bank slot
const withdrawOne = await bot.withdrawItem(0);  // withdraws 1 from slot 0
const withdrawAll = await bot.withdrawItem(0, -1);  // withdraws all from slot 0

// Close bank
await bot.closeBank();

success on deposit/withdraw means the full requested amount moved. A short fill returns success: false with partial: true, so check the amount instead of assuming you got everything:

const ore = await bot.withdrawItem(/iron ore/i, 28);
if (!ore.success) {
    console.log(`Only got ${ore.amountWithdrawn} of ${ore.requestedAmount} (${ore.reason})`);
}

Depositing Items

// Deposit specific item
const ore = sdk.getState()?.inventory.find(i => /ore$/i.test(i.name));
if (ore) {
    await sdk.sendBankDeposit(ore.slot, ore.count);
    await new Promise(r => setTimeout(r, 200));
}

// Deposit all of a type
const ores = sdk.getState()?.inventory.filter(i => /ore$/i.test(i.name)) ?? [];
for (const ore of ores) {
    await sdk.sendBankDeposit(ore.slot, ore.count);
    await new Promise(r => setTimeout(r, 200));
}

Deposit All of an Item

Use -1 as the quantity to deposit all of an item type.

// High-level (recommended)
await bot.depositItem(/bones/i, -1);  // Deposits ALL bones (even if in 5 separate slots)
await bot.depositItem(/coins/i, -1);  // Deposits ALL coins (stacked)

// Low-level
await sdk.sendBankDeposit(slot, -1);  // Deposits ALL items of that type from ANY slot

Withdrawing Items

// bankSlot is the position in the bank, not inventory
await sdk.sendBankWithdraw(bankSlot, count);

Closing the Bank

// High-level (recommended)
await bot.closeBank();

// Low-level (works for any modal interface)
await sdk.sendCloseModal();
await new Promise(r => setTimeout(r, 500));

Bank Locations (THERE IS NOT BANK IN LUMBRIDGE in 2004scape)

Bank Coordinates Notes
Varrock West (3185, 3436) Close to GE
Draynor (3092, 3243) Ground floor
Al Kharid (3269, 3167) Requires toll or quest
... others

Full Banking Loop Pattern

async function bankTrip(itemPattern, bankCoords, returnCoords) {
    // Walk to bank
    await bot.walkTo(bankCoords.x, bankCoords.z);

    // Open bank (automatically finds banker/booth)
    const openResult = await bot.openBank();
    if (!openResult.success) {
        console.log(`Failed to open bank: ${openResult.message}`);
        return;
    }

    // Deposit all items matching pattern (one call deposits ALL, even non-stackable)
    await bot.depositItem(itemPattern, -1);

    // Close bank and return
    await bot.closeBank();
    await bot.walkTo(returnCoords.x, returnCoords.z);
}

Key Learnings from Cow hide Banking

  1. Gate exit threshold: Use z < 3268 not z < 3265 for cow field
  2. Let walkTo handle the gate first: It attempts to open doors on its route
  3. Recover explicitly: If movement fails at the south gate (3253, 3266), inspect its current options, call openDoor(gate) when Open is present, then retry walkTo
  4. Banking works at Varrock West: (3185, 3436) confirmed working