(function () { "use strict"; const hasSettings = !!document.getElementById("options-settings-root"); const hasPageFunds = !!document.getElementById("options-funds-card"); if (!hasSettings && !hasPageFunds) return; async function apiJson(url, opts) { const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {})); return r.json(); } function refreshFundsAfterMutation() { if (typeof refreshAccountSnapshot !== "function") return; refreshAccountSnapshot({ force: true }); setTimeout(function () { refreshAccountSnapshot({ force: true, silent: true }); }, 1500); } function setMsg(id, text, isErr) { const el = document.getElementById(id); if (!el) return; el.textContent = text || ""; el.classList.toggle("opt-error", !!isErr); el.classList.toggle("opt-success", !!text && !isErr); } function fmtAmt(amount, ccy) { return `${Number(amount).toFixed(2)} ${ccy}`; } function accountLabel(acct) { return acct === "trading" ? "交易账户" : "资金账户"; } function swapDirLabel(dir) { return dir === "usdc_to_usdt" ? "USDC → USDT" : "USDT → USDC"; } function confirmOk(message) { return window.confirm(message); } function roundAvail(v) { const n = Number(v); if (!Number.isFinite(n) || n <= 0) return null; return Math.round(n * 100) / 100; } async function loadBalances(force, scope) { const parts = []; if (force) parts.push("force=1"); if (scope && scope !== "main") parts.push("scope=" + encodeURIComponent(scope)); const q = parts.length ? "?" + parts.join("&") : ""; const d = await apiJson("/api/options/balances" + q); if (!d.ok) throw new Error(d.msg || "余额拉取失败"); return d; } function pickBalance(bal, account, ccy) { const acct = account === "trading" ? "trading" : "funding"; const c = String(ccy || "").toLowerCase(); const availKey = acct + "_" + c + "_avail"; const totalKey = acct + "_" + c; return roundAvail(bal[availKey] != null ? bal[availKey] : bal[totalKey]); } async function resolveSwapMaxAmount(dir) { const bal = await loadBalances(true, "main"); const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT"; let amount = pickBalance(bal, "trading", ccy); let source = "trading"; if (!amount) { const fundingAmt = pickBalance(bal, "funding", ccy); if (fundingAmt) { amount = fundingAmt; source = "funding"; } } return { amount, bal, ccy, source }; } async function resolveMaxAmount(account, ccy, scope) { const bal = await loadBalances(true, scope || "main"); return pickBalance(bal, account, ccy); } function setButtonsBusy(btnIds, amountId, busy, busyText) { btnIds.forEach(function (id) { const btn = document.getElementById(id); if (!btn) return; if (busy) { if (!btn.dataset.origText) btn.dataset.origText = btn.textContent; btn.disabled = true; if (busyText) btn.textContent = busyText; } else { btn.disabled = false; if (btn.dataset.origText) { btn.textContent = btn.dataset.origText; delete btn.dataset.origText; } } }); const input = document.getElementById(amountId); if (input) input.disabled = busy; } function hardenAmountAutofill(ids) { ids.forEach(function (id) { const el = document.getElementById(id); if (!el) return; function wipe() { const v = String(el.value || "").trim(); if (/^[a-z][a-z0-9._-]{1,31}$/i.test(v)) el.value = ""; } wipe(); el.setAttribute("readonly", "readonly"); el.addEventListener("focus", function () { el.removeAttribute("readonly"); }); el.addEventListener("blur", function () { if (!el.value) el.setAttribute("readonly", "readonly"); }); setTimeout(wipe, 200); setTimeout(wipe, 800); setTimeout(wipe, 2000); }); } function bindFundsUi(ids) { const swapBtn = document.getElementById(ids.swapBtn); if (!swapBtn) return; const swapBtns = [ids.swapBtn, ids.swapAllBtn]; const intBtns = [ids.intBtn, ids.intAllBtn]; async function submitSwap(amount) { setButtonsBusy(swapBtns, ids.swapAmount, true, "兑换中…"); setMsg(ids.swapMsg, "兑换中,市价成交可能有延时…", false); try { const d = await apiJson("/api/options/spot/swap", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ direction: document.getElementById(ids.swapDir).value, amount: amount, }), }); if (d.ok) { setMsg(ids.swapMsg, "兑换成功", false); refreshFundsAfterMutation(); } else { setMsg(ids.swapMsg, "兑换失败:" + (d.msg || "未知错误"), true); } return d; } catch (e) { setMsg(ids.swapMsg, "兑换失败:" + (e.message || "网络错误"), true); return { ok: false }; } finally { setButtonsBusy(swapBtns, ids.swapAmount, false); } } async function submitInternalTransfer(amount) { setButtonsBusy(intBtns, ids.intAmount, true, "划转中…"); setMsg(ids.intMsg, "划转中…", false); try { const d = await apiJson("/api/options/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ccy: document.getElementById(ids.intCcy).value, from: document.getElementById(ids.intFrom).value, to: document.getElementById(ids.intTo).value, amount: amount, }), }); if (d.ok) { setMsg(ids.intMsg, "划转成功", false); refreshFundsAfterMutation(); } else { setMsg(ids.intMsg, "划转失败:" + (d.msg || "未知错误"), true); } return d; } catch (e) { setMsg(ids.intMsg, "划转失败:" + (e.message || "网络错误"), true); return { ok: false }; } finally { setButtonsBusy(intBtns, ids.intAmount, false); } } swapBtn.addEventListener("click", async function () { const amount = parseFloat(document.getElementById(ids.swapAmount).value); if (!amount || amount <= 0) { setMsg(ids.swapMsg, "请输入有效数量", true); return; } await submitSwap(amount); }); const swapAllBtn = document.getElementById(ids.swapAllBtn); if (swapAllBtn) { swapAllBtn.addEventListener("click", async function () { try { const dir = document.getElementById(ids.swapDir).value; const { amount, bal, ccy, source } = await resolveSwapMaxAmount(dir); if (!amount) { const fu = bal.funding_usdt_avail != null ? bal.funding_usdt_avail : bal.funding_usdt; const tu = bal.trading_usdt_avail != null ? bal.trading_usdt_avail : bal.trading_usdt; const fc = bal.funding_usdc_avail != null ? bal.funding_usdc_avail : bal.funding_usdc; setMsg( ids.swapMsg, "资金账户可用 " + ccy + " 不足(资金户 USDT:" + (fu != null ? fu : "—") + " USDC:" + (fc != null ? fc : "—") + "; 交易户 USDT:" + (tu != null ? tu : "—") + ")", true ); return; } const srcLabel = source === "trading" ? "交易账户" : "资金账户"; const msg = "确认全部兑换?\n\n" + "方向:" + swapDirLabel(dir) + "\n" + "金额:" + fmtAmt(amount, ccy) + "\n" + "来源:" + srcLabel + "\n\n" + "将按该账户可用余额发起市价兑换(可能有延时)。请确认。"; if (!confirmOk(msg)) return; document.getElementById(ids.swapAmount).value = String(amount); await submitSwap(amount); } catch (e) { setMsg(ids.swapMsg, "兑换失败:" + (e.message || "余额拉取失败"), true); } }); } const intBtn = document.getElementById(ids.intBtn); if (intBtn) { intBtn.addEventListener("click", async function () { const amount = parseFloat(document.getElementById(ids.intAmount).value); if (!amount || amount <= 0) { setMsg(ids.intMsg, "请输入有效数量", true); return; } await submitInternalTransfer(amount); }); } const intAllBtn = document.getElementById(ids.intAllBtn); if (intAllBtn) { intAllBtn.addEventListener("click", async function () { try { const ccy = document.getElementById(ids.intCcy).value; const from = document.getElementById(ids.intFrom).value; const to = document.getElementById(ids.intTo).value; const amount = await resolveMaxAmount(from, ccy, "main"); if (!amount) { setMsg(ids.intMsg, "划出账户可用余额不足", true); return; } const msg = "确认全部划转?\n\n" + "币种:" + ccy + "\n" + "划出:" + accountLabel(from) + "\n" + "划入:" + accountLabel(to) + "\n" + "金额:" + fmtAmt(amount, ccy) + "\n\n" + "将划转该账户全部可用余额。"; if (!confirmOk(msg)) return; document.getElementById(ids.intAmount).value = String(amount); await submitInternalTransfer(amount); } catch (e) { setMsg(ids.intMsg, "划转失败:" + (e.message || "余额拉取失败"), true); } }); } [ids.swapAllBtn, ids.intAllBtn].forEach(function (btnId) { const btn = document.getElementById(btnId); if (!btn) return; btn.addEventListener( "click", function () { const map = {}; map[ids.swapAllBtn] = ids.swapAmount; map[ids.intAllBtn] = ids.intAmount; const input = document.getElementById(map[btnId]); if (input) input.removeAttribute("readonly"); }, true ); }); hardenAmountAutofill([ids.swapAmount, ids.intAmount]); } if (hasSettings) { bindFundsUi({ swapDir: "opt-set-swap-dir", swapAmount: "opt-set-swap-amount", swapBtn: "opt-set-swap-btn", swapAllBtn: "opt-set-swap-all-btn", swapMsg: "opt-set-swap-msg", intCcy: "opt-set-int-ccy", intFrom: "opt-set-int-from", intTo: "opt-set-int-to", intAmount: "opt-set-int-amount", intBtn: "opt-set-int-btn", intAllBtn: "opt-set-int-all-btn", intMsg: "opt-set-int-msg", }); } if (hasPageFunds) { bindFundsUi({ swapDir: "opt-page-swap-dir", swapAmount: "opt-page-swap-amount", swapBtn: "opt-page-swap-btn", swapAllBtn: "opt-page-swap-all-btn", swapMsg: "opt-page-swap-msg", intCcy: "opt-page-int-ccy", intFrom: "opt-page-int-from", intTo: "opt-page-int-to", intAmount: "opt-page-int-amount", intBtn: "opt-page-int-btn", intAllBtn: "opt-page-int-all-btn", intMsg: "opt-page-int-msg", }); } })();