Bitburner Hacknet Script Revisited: What I’d Change Four Years On
A cleaner, payback-driven script for automating Hacknet nodes, and what I got wrong the first time
Back in January 2022 I wrote a quick walkthrough of my Hacknet script for Bitburner. It was three minutes long, had a typo in roughly every other paragraph, and referred to “lines 81–92 above” when there was nothing above. It is, by a distance, the most-read thing I have ever written.
So it seems only fair to go back and do it properly.
If you haven’t met it, Bitburner is a semi-idle hacking game where you get things done by writing JavaScript. The Hacknet is one of the first money-makers you meet: a set of virtual machines that earn you income, and earn more the more you upgrade them. It’s a lovely little optimisation problem, and it’s where a lot of players write their first real script.
The idea still holds: optimise for payback time
The core of the original post was right, and I’d keep it. With the Hacknet you can do four things:
- Upgrade a node’s level
- Upgrade a node’s RAM
- Upgrade a node’s cores
- Buy a new node
Each one costs money and raises your income. The question for any of them is the same: if I spend this, how many seconds until the extra income has paid it back?
payback time = cost / extra income per second
Work that out for every possible action, pick the one with the shortest payback, do it, and repeat. That’s a greedy algorithm, and for this problem greedy works well, because every action makes you money and the sooner you buy the good ones, the sooner you can afford the next.
Upgrading everything to max, which is what I did before I thought about it, gets that the wrong way round. Some upgrades pay back in minutes. Others take days. Spending on the slow ones first holds up everything else.
What I’d change
1. Stop treating the formula as a secret
In 2022 I got the production formula by reading the game’s source code, because the Formulas API costs in-game money and you lose it on every reset. That’s still a reasonable trick, but I’d now put the formula in one small, clearly labelled function, so it’s obvious where it came from and easy to swap out if the game changes it:
// Hacknet node income per second, transcribed from the game source.
// If you own Formulas.exe you can use
// ns.formulas.hacknetNodes.moneyGainRate() instead.
const rate = (level, ram, cores, mult) =>
level * 1.5 * Math.pow(1.035, ram - 1) * ((cores + 5) / 6) * mult;
Reading it tells you a lot about the game. Level adds income in a straight line. RAM gives a small percentage boost each time, and each RAM upgrade doubles the RAM, which is why it’s pricey. Cores give a flat step that gets relatively smaller as you add more. You can see the diminishing returns right there in the code.
2. Compare every action at once
My original script looped over each node and upgraded it until it hit a limit, then moved to the next. It worked, but it meant a cheap, fast-paying upgrade on node 5 waited while node 1 got a slow one. The better way is to build a list of every possible action across every node, score them all, and take the best.
3. Use your real multipliers
Augmentations and source files change how much your nodes earn. The game will tell you your current multiplier, so there’s no need to guess or ignore it:
const mult = ns.getHacknetMultipliers().production;
4. Wait sensibly
The old script checked constantly. If the best action costs more than you have, the script should just wait and check again in a second. It’s more polite to your browser, and you don’t lose anything by it.
The script
Here’s the whole thing. Save it as hacknet.js and run it with run hacknet.js.
/** @param {NS} ns */
export async function main(ns) {
const hn = ns.hacknet;
const MAX_PAYBACK = 12 * 60 * 60; // seconds: only buy what pays back within 12 hours
const mult = ns.getHacknetMultipliers().production;
const rate = (level, ram, cores) =>
level * 1.5 * Math.pow(1.035, ram - 1) * ((cores + 5) / 6) * mult;
ns.disableLog("ALL");
while (true) {
const options = [];
// Option: buy a new node (it starts at level 1, 1GB RAM, 1 core)
options.push({
desc: "buy new node",
cost: hn.getPurchaseNodeCost(),
gain: rate(1, 1, 1),
act: () => hn.purchaseNode(),
});
// Options: upgrade each existing node
for (let i = 0; i < hn.numNodes(); i++) {
const s = hn.getNodeStats(i);
const now = rate(s.level, s.ram, s.cores);
options.push({
desc: `node ${i} level`,
cost: hn.getLevelUpgradeCost(i, 1),
gain: rate(s.level + 1, s.ram, s.cores) - now,
act: () => hn.upgradeLevel(i, 1),
});
options.push({
desc: `node ${i} RAM`,
cost: hn.getRamUpgradeCost(i, 1),
gain: rate(s.level, s.ram * 2, s.cores) - now,
act: () => hn.upgradeRam(i, 1),
});
options.push({
desc: `node ${i} cores`,
cost: hn.getCoreUpgradeCost(i, 1),
gain: rate(s.level, s.ram, s.cores + 1) - now,
act: () => hn.upgradeCore(i, 1),
});
}
// Maxed-out upgrades report an infinite cost, so drop them, then rank by payback
const best = options
.filter((o) => Number.isFinite(o.cost) && o.gain > 0)
.map((o) => ({ ...o, payback: o.cost / o.gain }))
.sort((a, b) => a.payback - b.payback)[0];
if (!best || best.payback > MAX_PAYBACK) {
ns.print("Nothing worth buying. Waiting a minute.");
await ns.sleep(60_000);
continue;
}
if (ns.getServerMoneyAvailable("home") >= best.cost) {
best.act();
ns.print(`Bought ${best.desc}: pays back in ${Math.round(best.payback)}s`);
} else {
await ns.sleep(1000);
}
}
}
Run tail hacknet.js to watch it think.
A couple of caveats
The payback figures leave out your BitNode multiplier, which scales every node equally. That doesn’t change which action is best, but it does mean the real payback time can be a bit longer or shorter than the script reports. If you’re in a BitNode that penalises Hacknet income, lower MAX_PAYBACK to taste.
The 12-hour limit is a judgement call, not a law. Early in a run, when you’ll reset soon, a shorter limit makes sense: there’s no point buying something that pays back after you’ve installed your augmentations. Later on, a longer one is fine.
What I learned from the first post
The original post had rough edges, but it worked because it solved a real problem that real players had, and it gave them code they could use. That’s still the bit I’d keep. The typos I can do without.
If you’re playing Bitburner, I’d love to hear what strategy you use for the Hacknet, especially if you’ve found one that beats payback time. And if you want more of this sort of thing, follow along: I’m writing five posts a week between now and Christmas, covering code, architecture, government tech and, on Fridays, sailing.
Tomorrow: what the GOV.UK passkeys rollout means for you, and why you should be asking your bank for the same.