Invest more than 10,000,000 criptoe in one go. Also accepts a percentage of your uninvested balance ("50%") and adds 25/50/75/100% quick buttons.
// ==UserScript==
// @name IdlePixel - Criptoe Bulk Invest
// @namespace com.eisa.idlepixel
// @version 1.0.0
// @description Invest more than 10,000,000 criptoe in one go. Also accepts a percentage of your uninvested balance ("50%") and adds 25/50/75/100% quick buttons.
// @author Eisa (StoneSinew)
// @license MIT
// @match *://idle-pixel.com/login/play*
// @grant none
// @icon https://cdn.idle-pixel.com/images/criptoe_coin.png
// @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==
(function () {
"use strict";
const PLUGIN_ID = "criptoe_bulk_invest";
const STORAGE_KEY = "criptoe_bulk_invest_config";
const IMG = "https://cdn.idle-pixel.com/images/";
const WIRE_MAX = 10000000;
const DEFAULT_INTERVAL_S = 5;
const MIN_INTERVAL_S = 1;
const MAX_INTERVAL_S = 120;
const MAX_SENDS = 500000;
const WALLETS = [1, 2, 3, 4];
class CriptoeBulkInvestPlugin extends IdlePixelPlusPlugin {
constructor() {
super(PLUGIN_ID, {
about: {
name: GM_info.script.name,
version: GM_info.script.version,
author: GM_info.script.author,
description: GM_info.script.description,
},
});
this.cfg = this.loadCfg();
this.dlg = null;
this.queue = null;
this._timer = null;
this._ui = null;
this._origInvest = null;
}
loadCfg() {
try {
const raw = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
const cfg = Object.assign({ intervalS: DEFAULT_INTERVAL_S }, raw);
cfg.intervalS = this.clampInterval(cfg.intervalS);
return cfg;
} catch (e) { return { intervalS: DEFAULT_INTERVAL_S }; }
}
saveCfg() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.cfg)); } catch (e) { } }
clampInterval(v) {
let n = parseFloat(v);
if (!isFinite(n)) n = DEFAULT_INTERVAL_S;
if (n < MIN_INTERVAL_S) n = MIN_INTERVAL_S;
if (n > MAX_INTERVAL_S) n = MAX_INTERVAL_S;
return n;
}
intervalMs() { return Math.round(this.clampInterval(this.cfg.intervalS) * 1000); }
num(k) { try { return IdlePixelPlus.getVarOrDefault(k, 0, "int"); } catch (e) { return 0; } }
bal() { return Math.max(0, this.num("criptoe")); }
invested(n) { return this.num("wallet" + n + "_invested"); }
commas(n) { return String(Math.floor(Number(n) || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); }
esc(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); }
isSundayUtc() { try { return new Date().getUTCDay() === 0; } catch (e) { return false; } }
parseAmount(raw, bal) {
const s = String(raw == null ? "" : raw).trim().toLowerCase().replace(/[,\s_]/g, "");
if (!s) return { error: "enter an amount" };
if (s.charAt(s.length - 1) === "%") {
const pct = parseFloat(s.slice(0, -1));
if (!isFinite(pct) || pct <= 0) return { error: "percent must be greater than 0" };
if (pct > 100) return { error: "percent can't be above 100" };
return { amount: Math.floor(bal * pct / 100), pct: pct };
}
const m = /^(\d+(?:\.\d+)?)([kmb])?$/.exec(s);
if (!m) return { error: "not a number — try 25000000, 25m or 50%" };
let v = parseFloat(m[1]);
if (m[2] === "k") v *= 1e3;
else if (m[2] === "m") v *= 1e6;
else if (m[2] === "b") v *= 1e9;
v = Math.floor(v);
if (!isFinite(v) || v <= 0) return { error: "amount must be greater than 0" };
return { amount: v };
}
planChunks(total) {
const out = [];
let left = Math.floor(total);
while (left > 0 && out.length < MAX_SENDS) {
const c = Math.min(WIRE_MAX, left);
out.push(c);
left -= c;
}
return { chunks: out, truncated: left > 0, dropped: left };
}
etaText(sendCount) {
const secs = Math.max(0, sendCount - 1) * this.clampInterval(this.cfg.intervalS);
if (secs <= 0) return "instant";
if (secs < 60) return "~" + Math.round(secs) + "s";
const m = Math.floor(secs / 60), s = Math.round(secs % 60);
return "~" + m + "m" + (s ? " " + s + "s" : "");
}
onLogin() {
try { this.hookInvestDialogue(); } catch (e) { console.error("Criptoe Bulk Invest: hook failed", e); }
try { this.injectStyle(); } catch (e) { console.error("Criptoe Bulk Invest: style failed", e); }
try { this.injectPanelBox(); } catch (e) { console.error("Criptoe Bulk Invest: panel inject failed", e); }
}
onPanelChanged() {
try { this.injectPanelBox(); } catch (e) { }
}
onVariableSet(key) {
if (key === "criptoe" || /^wallet[1-4]_invested$/.test(String(key))) {
if (this.dlg) this.refreshDialog();
if (this.queue) this.renderProgress();
this.refreshPanelBox();
}
}
onConfigsChanged() {}
hookInvestDialogue() {
if (typeof Modals === "undefined" || !Modals || typeof Modals.open_criptoe_invest_dialogue !== "function") {
console.warn("Criptoe Bulk Invest: Modals.open_criptoe_invest_dialogue not found — leaving the game's dialogue alone.");
return;
}
if (this._origInvest) return;
this._origInvest = Modals.open_criptoe_invest_dialogue;
const self = this;
Modals.open_criptoe_invest_dialogue = function (walletId) {
try { self.openDialog(parseInt(walletId, 10)); }
catch (e) {
console.error("Criptoe Bulk Invest: dialog failed, falling back to native dialog", e);
try { self._origInvest.call(Modals, walletId); } catch (e2) { }
}
};
}
injectStyle() {
if (document.getElementById("cbi-style")) return;
const st = document.createElement("style");
st.id = "cbi-style";
st.textContent =
"#cbi-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:20000;display:flex;align-items:center;justify-content:center;}" +
"#cbi-dialog{background:#343e59;color:#fff;border:1px solid #6d7ba0;border-radius:10px;padding:18px 20px;width:min(560px,92vw);max-height:90vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);}" +
"#cbi-dialog h3{margin:0 0 4px 0;font-size:1.15em;}" +
"#cbi-dialog .cbi-sub{color:#b9c2d8;font-size:.86em;margin-bottom:12px;}" +
"#cbi-dialog .cbi-bal{background:#2b2d3e;border-radius:6px;padding:8px 10px;margin-bottom:12px;font-size:.92em;}" +
"#cbi-dialog .cbi-bal b{color:#ffd700;}" +
"#cbi-dialog input[type=text]{color:#000;width:100%;box-sizing:border-box;padding:7px 9px;border-radius:6px;border:1px solid #888;font-size:1.05em;}" +
"#cbi-box input[type=number]{color:#000;width:78px;padding:4px 6px;border-radius:5px;border:1px solid #888;}" +
"#cbi-row-pct{display:flex;gap:8px;flex-wrap:wrap;margin-top:9px;}" +
".cbi-btn{cursor:pointer;padding:5px 13px;border:1px solid #8a97bd;border-radius:6px;background:#3f4a69;display:inline-block;user-select:none;font-size:.92em;}" +
".cbi-btn:hover{background:#4d5a80;}" +
".cbi-btn.cbi-primary{background:#2e7d32;border-color:#4caf50;}" +
".cbi-btn.cbi-primary:hover{background:#388e3c;}" +
".cbi-btn.cbi-danger{background:#8e2f2f;border-color:#c05c5c;}" +
".cbi-btn.cbi-danger:hover{background:#a33a3a;}" +
".cbi-btn.cbi-off{opacity:.45;cursor:not-allowed;}" +
".cbi-warn{color:#ffb347;} .cbi-err{color:#ff8080;} .cbi-ok{color:#9ee493;}" +
"#cbi-preview{margin-top:12px;background:#2b2d3e;border-radius:6px;padding:9px 11px;font-size:.9em;line-height:1.5;min-height:2.4em;}" +
"#cbi-dialog .cbi-note{color:#b9c2d8;font-size:.82em;margin-top:10px;line-height:1.45;}" +
"#cbi-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:14px;}" +
"#cbi-progress{position:fixed;right:18px;bottom:18px;z-index:19500;background:#343e59;color:#fff;border:1px solid #6d7ba0;border-radius:10px;padding:12px 14px;width:300px;box-shadow:0 6px 26px rgba(0,0,0,.55);font-size:.9em;}" +
"#cbi-progress .cbi-ptitle{font-weight:bold;margin-bottom:6px;}" +
"#cbi-progress .cbi-bar{height:7px;background:#22243a;border-radius:4px;overflow:hidden;margin:8px 0;}" +
"#cbi-progress .cbi-fill{height:100%;background:#4caf50;transition:width .25s;}" +
"#cbi-progress .cbi-pline{color:#c8d0e4;line-height:1.5;}" +
"#cbi-progress .cbi-pdone{color:#9ee493;} #cbi-progress .cbi-pstop{color:#ffb347;}" +
"#cbi-progress .cbi-pactions{display:flex;gap:8px;justify-content:flex-end;margin-top:9px;}" +
"#cbi-box{margin:14px 0;padding:12px 14px;border:1px solid #666;border-radius:8px;max-width:820px;background:rgba(0,0,0,.18);}" +
"#cbi-box .cbi-btitle{font-weight:bold;margin-bottom:5px;}" +
"#cbi-box .cbi-brow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:8px;}" +
"#cbi-box .cbi-bnote{color:#b9c2d8;font-size:.85em;line-height:1.5;}";
document.head.appendChild(st);
}
injectPanelBox() {
const panel = document.getElementById("panel-criptoe-market");
if (!panel || document.getElementById("cbi-box")) return;
const box = document.createElement("div");
box.id = "cbi-box";
box.innerHTML =
'<div class="cbi-btitle"><img src="' + IMG + 'criptoe_coin.png" style="height:18px;vertical-align:middle;"> Criptoe Bulk Invest</div>' +
'<div class="cbi-bnote">A single quantity entry is capped at ' + this.commas(WIRE_MAX) + '. Use <b>Add Criptoe</b> as normal and enter any larger amount,' +
'it is split into back-to-back investments of at most ' + this.commas(WIRE_MAX) + ' each. You can also enter a percentage of your uninvested balance, like <b>50%</b>.</div>' +
'<div class="cbi-brow">' +
'<label>Seconds between sends: <input id="cbi-interval" type="number" min="' + MIN_INTERVAL_S + '" max="' + MAX_INTERVAL_S + '" step="0.5" value="' + this.cfg.intervalS + '"></label>' +
'<span class="cbi-bnote">Criptoe investing is rate-limited, raise this number if sends start getting dropped.</span>' +
'</div>' +
'<div class="cbi-brow"><span id="cbi-box-status" class="cbi-bnote"></span></div>';
const anchor = panel.querySelector(".charts-content");
if (anchor) panel.insertBefore(box, anchor); else panel.appendChild(box);
const self = this;
const inp = box.querySelector("#cbi-interval");
inp.addEventListener("change", function () {
const v = self.clampInterval(this.value);
this.value = v;
self.cfg.intervalS = v;
self.saveCfg();
self.refreshPanelBox();
});
this.refreshPanelBox();
}
refreshPanelBox() {
const el = document.getElementById("cbi-box-status");
if (!el) return;
const b = this.bal();
let txt = "Uninvested right now: " + this.commas(b) + " criptoe";
if (b > WIRE_MAX) txt += " — that is " + this.planChunks(b).chunks.length + " send(s) to invest in full.";
if (this.queue) txt += " · a queue is running (see the panel in the corner).";
el.textContent = txt;
}
openDialog(walletId) {
const w = WALLETS.indexOf(walletId) >= 0 ? walletId : 1;
this.closeDialog();
this.injectStyle();
this.dlg = { wallet: w };
const back = document.createElement("div");
back.id = "cbi-backdrop";
back.innerHTML =
'<div id="cbi-dialog" role="dialog" aria-modal="true">' +
'<h3><img src="' + IMG + 'criptoe_coin.png" style="height:20px;vertical-align:middle;"> Add Investment — Wallet ' + w + '</h3>' +
'<div class="cbi-sub">Amounts above ' + this.commas(WIRE_MAX) + ' are sent as several investments in a row.</div>' +
'<div class="cbi-bal" id="cbi-balline"></div>' +
'<input type="text" id="cbi-amount" placeholder="e.g. 25000000, 25m, or 50%" autocomplete="off">' +
'<div id="cbi-row-pct">' +
'<span class="cbi-btn cbi-pct" data-pct="25">25%</span>' +
'<span class="cbi-btn cbi-pct" data-pct="33">33%</span>' +
'<span class="cbi-btn cbi-pct" data-pct="50">50%</span>' +
'<span class="cbi-btn cbi-pct" data-pct="75">75%</span>' +
'<span class="cbi-btn cbi-pct" data-pct="100">100%</span>' +
'</div>' +
'<div id="cbi-preview"></div>' +
'<div class="cbi-note">' +
'<b>Note that it is impossible to withdraw criptoe from a wallet you invested in on the same day.</b>' +
(this.isSundayUtc() ? '<br><span class="cbi-warn">It is Sunday (UTC) — wallet percentages are not published today.</span>' : '') +
'</div>' +
'<div id="cbi-actions">' +
'<span class="cbi-btn" id="cbi-cancel">Cancel</span>' +
'<span class="cbi-btn cbi-primary" id="cbi-go">Invest</span>' +
'</div>' +
'</div>';
document.body.appendChild(back);
const self = this;
const input = back.querySelector("#cbi-amount");
back.querySelectorAll(".cbi-pct").forEach(function (b) {
b.addEventListener("click", function () {
input.value = this.getAttribute("data-pct") + "%";
self.refreshDialog();
input.focus();
});
});
input.addEventListener("input", function () { self.refreshDialog(); });
input.addEventListener("keydown", function (ev) {
if (ev.key === "Enter") { ev.preventDefault(); self.submitDialog(); }
else if (ev.key === "Escape") { ev.preventDefault(); self.closeDialog(); }
});
back.querySelector("#cbi-cancel").addEventListener("click", function () { self.closeDialog(); });
back.querySelector("#cbi-go").addEventListener("click", function () { self.submitDialog(); });
back.addEventListener("mousedown", function (ev) { if (ev.target === back) self.closeDialog(); });
this.refreshDialog();
try { input.focus(); } catch (e) { }
}
closeDialog() {
const el = document.getElementById("cbi-backdrop");
if (el && el.parentNode) el.parentNode.removeChild(el);
this.dlg = null;
}
evaluate() {
const bal = this.bal();
const inp = document.getElementById("cbi-amount");
const parsed = this.parseAmount(inp ? inp.value : "", bal);
if (parsed.error) return { bal: bal, error: parsed.error };
if (bal <= 0) return { bal: bal, error: "you have no uninvested criptoe" };
let amount = parsed.amount;
let clamped = false;
if (amount > bal) { amount = bal; clamped = true; }
if (amount <= 0) return { bal: bal, error: "that rounds down to 0 criptoe" };
const plan = this.planChunks(amount);
return {
bal: bal, amount: amount, clamped: clamped, pct: parsed.pct,
chunks: plan.chunks, truncated: plan.truncated, dropped: plan.dropped,
};
}
refreshDialog() {
if (!this.dlg) return;
const w = this.dlg.wallet;
const balLine = document.getElementById("cbi-balline");
if (balLine) {
balLine.innerHTML =
"Uninvested: <b>" + this.commas(this.bal()) + "</b> criptoe" +
" · already in wallet " + w + ": <b>" + this.commas(this.invested(w)) + "</b>";
}
const pv = document.getElementById("cbi-preview");
const go = document.getElementById("cbi-go");
if (!pv) return;
const ev = this.evaluate();
if (ev.error) {
pv.innerHTML = '<span class="cbi-err">' + this.esc(ev.error) + "</span>";
if (go) go.classList.add("cbi-off");
return;
}
if (go) go.classList.remove("cbi-off");
const n = ev.chunks.length;
let html = '<span class="cbi-ok">Investing ' + this.commas(ev.amount) + " criptoe</span> into wallet " + w + "<br>";
if (n === 1) {
html += "1 send — within the " + this.commas(WIRE_MAX) + " cap.";
} else {
const full = ev.chunks.filter(function (c) { return c === WIRE_MAX; }).length;
const rem = ev.chunks[n - 1] === WIRE_MAX ? 0 : ev.chunks[n - 1];
html += "<b>" + n + " sends</b> — " + full + " x " + this.commas(WIRE_MAX) +
(rem ? " then " + this.commas(rem) : "") +
" · " + this.etaText(n) + " at " + this.clampInterval(this.cfg.intervalS) + "s apart";
}
if (ev.pct) html += '<br><span class="cbi-warn">' + ev.pct + "% of your " + this.commas(ev.bal) + " uninvested balance.</span>";
if (ev.clamped) html += '<br><span class="cbi-warn">Reduced to your full uninvested balance of ' + this.commas(ev.bal) + ".</span>";
if (ev.truncated) html += '<br><span class="cbi-warn">Capped at ' + MAX_SENDS + " sends — " + this.commas(ev.dropped) + " criptoe left over. Run it again afterwards.</span>";
pv.innerHTML = html;
}
submitDialog() {
if (!this.dlg) return;
if (this.queue) {
alert("A criptoe investment queue is already running. Cancel it first, or wait for it to finish.");
return;
}
const ev = this.evaluate();
if (ev.error) { this.refreshDialog(); return; }
const w = this.dlg.wallet;
if (ev.chunks.length > 1) {
const msg = "Invest " + this.commas(ev.amount) + " criptoe into wallet " + w + "?\n\n" +
"This is sent as " + ev.chunks.length + " separate investments of at most " + this.commas(WIRE_MAX) +
", about " + this.clampInterval(this.cfg.intervalS) + "s apart (" + this.etaText(ev.chunks.length) + " total).\n\n" +
"You cannot withdraw from a wallet on the same day you invest in it.";
if (!confirm(msg)) return;
}
this.closeDialog();
this.startQueue(w, ev.chunks);
}
startQueue(wallet, chunks) {
this.queue = {
wallet: wallet,
chunks: chunks,
i: 0,
planned: chunks.reduce(function (a, b) { return a + b; }, 0),
sentTotal: 0,
nextAt: 0,
note: "",
done: false,
};
this.refreshPanelBox();
this.renderProgress();
const self = this;
this._ui = setInterval(function () { if (self.queue) self.renderProgress(); }, 1000);
this.pump();
}
pump() {
const q = this.queue;
if (!q || q.done) return;
if (q.i >= q.chunks.length) {
this.finishQueue("All " + this.commas(q.sentTotal) + " criptoe invested.", "done");
return;
}
const balNow = this.bal();
const amt = Math.min(q.chunks[q.i], balNow);
if (amt <= 0) {
this.finishQueue("Stopped — no uninvested criptoe left. " + this.commas(q.sentTotal) +
" of " + this.commas(q.planned) + " invested.", "stop");
return;
}
if (amt < q.chunks[q.i]) q.note = "last send trimmed to your remaining balance";
try {
IdlePixelPlus.sendMessage("INVEST_WALLET=wallet_" + q.wallet + "~" + amt);
} catch (e) {
console.error("Criptoe Bulk Invest: send failed", e);
this.finishQueue("Send failed — stopped after " + this.commas(q.sentTotal) + ".", "stop");
return;
}
q.sentTotal += amt;
q.i++;
if (q.i >= q.chunks.length) {
this.finishQueue("All " + this.commas(q.sentTotal) + " criptoe invested into wallet " + q.wallet + ".", "done");
return;
}
const gap = this.intervalMs();
q.nextAt = Date.now() + gap;
const self = this;
this._timer = setTimeout(function () { self.pump(); }, gap);
this.renderProgress();
}
cancelQueue() {
const q = this.queue;
if (!q) return;
this.finishQueue("Cancelled — " + this.commas(q.sentTotal) + " of " + this.commas(q.planned) +
" invested (" + q.i + " of " + q.chunks.length + " sends).", "stop");
}
finishQueue(msg, kind) {
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
if (this._ui) { clearInterval(this._ui); this._ui = null; }
if (this.queue) {
this.queue.done = true;
this.queue.finalMsg = msg;
this.queue.finalKind = kind;
}
this.renderProgress();
this.queue = null;
this.refreshPanelBox();
try { console.log("Criptoe Bulk Invest:", msg); } catch (e) { }
setTimeout(function () {
const el = document.getElementById("cbi-progress");
if (el && el.getAttribute("data-final") === "1" && el.parentNode) el.parentNode.removeChild(el);
}, kind === "done" ? 9000 : 14000);
}
renderProgress() {
const q = this.queue;
if (!q) return;
let el = document.getElementById("cbi-progress");
if (!el) {
el = document.createElement("div");
el.id = "cbi-progress";
document.body.appendChild(el);
}
const total = q.chunks.length;
const pctDone = total ? Math.round((q.i / total) * 100) : 100;
const self = this;
if (q.done) {
el.setAttribute("data-final", "1");
el.innerHTML =
'<div class="cbi-ptitle"><img src="' + IMG + 'criptoe_coin.png" style="height:16px;vertical-align:middle;"> Wallet ' + q.wallet + '</div>' +
'<div class="cbi-pline ' + (q.finalKind === "done" ? "cbi-pdone" : "cbi-pstop") + '">' + this.esc(q.finalMsg || "") + '</div>' +
'<div class="cbi-pactions"><span class="cbi-btn" id="cbi-pclose">Close</span></div>';
const c = el.querySelector("#cbi-pclose");
if (c) c.addEventListener("click", function () {
const e2 = document.getElementById("cbi-progress");
if (e2 && e2.parentNode) e2.parentNode.removeChild(e2);
});
return;
}
el.removeAttribute("data-final");
const waiting = Math.max(0, Math.ceil((q.nextAt - Date.now()) / 1000));
el.innerHTML =
'<div class="cbi-ptitle"><img src="' + IMG + 'criptoe_coin.png" style="height:16px;vertical-align:middle;"> Investing into Wallet ' + q.wallet + '</div>' +
'<div class="cbi-bar"><div class="cbi-fill" style="width:' + pctDone + '%;"></div></div>' +
'<div class="cbi-pline">Send <b>' + q.i + '</b> of <b>' + total + '</b> · ' +
this.commas(q.sentTotal) + ' of ' + this.commas(q.planned) + '</div>' +
'<div class="cbi-pline">' + (q.i >= total ? "finishing…" : "next send in " + waiting + "s") +
(q.note ? " · " + this.esc(q.note) : "") + '</div>' +
'<div class="cbi-pactions"><span class="cbi-btn cbi-danger" id="cbi-pcancel">Cancel</span></div>';
const b = el.querySelector("#cbi-pcancel");
if (b) b.addEventListener("click", function () { self.cancelQueue(); });
}
}
IdlePixelPlus.registerPlugin(new CriptoeBulkInvestPlugin());
})();