// GOLDBACK DENOMINATION PRICING (STACK-45)
// =============================================================================
// Manual-entry pricing for Goldback denominations.
// Follows priceHistory.js patterns: save/load/record with saveDataSync/loadDataSync.
// Data structures:
// goldbackPrices: { "1": { price: 5.12, updatedAt: 1707500000000 }, ... }
// goldbackPriceHistory: { "1": [{ ts: 1707500000000, price: 5.12 }, ...], ... }
// =============================================================================
/**
* Saves current Goldback denomination prices to localStorage.
*/
const saveGoldbackPrices = () => {
try {
saveDataSync(GOLDBACK_PRICES_KEY, goldbackPrices);
} catch (error) {
console.error('Error saving Goldback prices:', error);
}
};
/**
* Loads Goldback denomination prices from localStorage into global state.
*/
const loadGoldbackPrices = () => {
try {
const data = loadDataSync(GOLDBACK_PRICES_KEY, {});
goldbackPrices = (data && typeof data === 'object' && !Array.isArray(data)) ? data : {};
} catch (error) {
console.error('Error loading Goldback prices:', error);
goldbackPrices = {};
}
};
/**
* Saves Goldback price history to localStorage.
*/
const saveGoldbackPriceHistory = () => {
try {
saveDataSync(GOLDBACK_PRICE_HISTORY_KEY, goldbackPriceHistory);
} catch (error) {
console.error('Error saving Goldback price history:', error);
}
};
/**
* Loads Goldback price history from localStorage into global state.
*/
const loadGoldbackPriceHistory = () => {
try {
const data = loadDataSync(GOLDBACK_PRICE_HISTORY_KEY, {});
goldbackPriceHistory = (data && typeof data === 'object' && !Array.isArray(data)) ? data : {};
} catch (error) {
console.error('Error loading Goldback price history:', error);
goldbackPriceHistory = {};
}
};
/**
* Loads the Goldback pricing enabled toggle from localStorage.
*/
const loadGoldbackEnabled = () => {
try {
const val = loadDataSync(GOLDBACK_ENABLED_KEY, true);
goldbackEnabled = val === true;
} catch (error) {
console.error('Error loading Goldback enabled state:', error);
goldbackEnabled = true;
}
};
/**
* Saves the Goldback pricing enabled toggle to localStorage.
* @param {boolean} val - Whether Goldback pricing is enabled
*/
const saveGoldbackEnabled = (val) => {
goldbackEnabled = val === true;
try {
saveDataSync(GOLDBACK_ENABLED_KEY, goldbackEnabled);
} catch (error) {
console.error('Error saving Goldback enabled state:', error);
}
};
/**
* Appends current denomination prices as timestamped history entries.
* Called after user saves updated prices in the settings panel.
*/
const recordGoldbackPrices = () => {
const now = Date.now();
for (const key of Object.keys(goldbackPrices)) {
const entry = goldbackPrices[key];
if (!entry || typeof entry.price !== 'number' || entry.price <= 0) continue;
if (!goldbackPriceHistory[key]) {
goldbackPriceHistory[key] = [];
}
// Skip exact duplicate of last entry
const arr = goldbackPriceHistory[key];
const last = arr.length > 0 ? arr[arr.length - 1] : null;
if (last && last.price === entry.price) continue;
arr.push({ ts: now, price: entry.price });
}
saveGoldbackPriceHistory();
};
/**
* Returns the denomination price for a given Goldback weight, or null.
* @param {number} weightGb - Weight in Goldback denomination units (e.g. 1, 5, 10)
* @returns {number|null} Per-unit denomination price, or null if not set
*/
const getGoldbackDenominationPrice = (weightGb) => {
const key = String(weightGb);
const entry = goldbackPrices[key];
if (entry && typeof entry.price === 'number' && entry.price > 0) {
return entry.price;
}
return null;
};
/**
* Returns true if Goldback pricing is active (enabled + has at least one price).
* @returns {boolean}
*/
const isGoldbackPricingActive = () => {
if (!goldbackEnabled) return false;
for (const key of Object.keys(goldbackPrices)) {
if (goldbackPrices[key] && goldbackPrices[key].price > 0) return true;
}
return false;
};
// =============================================================================
// GOLDBACK REAL-TIME PRICE ESTIMATION (STACK-52)
// =============================================================================
/**
* Loads the Goldback estimation enabled toggle from localStorage.
*/
const loadGoldbackEstimateEnabled = () => {
try {
const val = loadDataSync(GOLDBACK_ESTIMATE_ENABLED_KEY, true);
goldbackEstimateEnabled = val === true;
} catch (error) {
console.error('Error loading Goldback estimate enabled state:', error);
goldbackEstimateEnabled = true;
}
};
/**
* Saves the Goldback estimation enabled toggle to localStorage.
* @param {boolean} val - Whether estimation is enabled
*/
const saveGoldbackEstimateEnabled = (val) => {
goldbackEstimateEnabled = val === true;
try {
saveDataSync(GOLDBACK_ESTIMATE_ENABLED_KEY, goldbackEstimateEnabled);
} catch (error) {
console.error('Error saving Goldback estimate enabled state:', error);
}
};
/**
* Loads the user-configurable premium modifier from localStorage.
*/
const loadGoldbackEstimateModifier = () => {
try {
const val = loadDataSync(GB_ESTIMATE_MODIFIER_KEY, GB_ESTIMATE_PREMIUM);
const num = parseFloat(val);
goldbackEstimateModifier = (!isNaN(num) && num > 0) ? num : GB_ESTIMATE_PREMIUM;
} catch (error) {
console.error('Error loading Goldback estimate modifier:', error);
goldbackEstimateModifier = GB_ESTIMATE_PREMIUM;
}
};
/**
* Saves the user-configurable premium modifier to localStorage.
* @param {number} val - Modifier value (e.g. 1.0, 1.03)
*/
const saveGoldbackEstimateModifier = (val) => {
const num = parseFloat(val);
goldbackEstimateModifier = (!isNaN(num) && num > 0) ? num : GB_ESTIMATE_PREMIUM;
try {
saveDataSync(GB_ESTIMATE_MODIFIER_KEY, goldbackEstimateModifier);
} catch (error) {
console.error('Error saving Goldback estimate modifier:', error);
}
};
/**
* Computes the estimated 1 Goldback exchange rate from gold spot price.
* Formula: 2 × (goldSpot / 1000) × modifier
* @param {number} goldSpot - Current gold spot price per troy oz
* @returns {number} Estimated 1 Goldback rate in USD
*/
const computeGoldbackEstimatedRate = (goldSpot) => {
return 2 * (goldSpot / 1000) * goldbackEstimateModifier;
};
/**
* Hook called whenever the gold spot price changes (API sync, manual, cache).
* If estimation is ON + Goldback pricing is ON + valid gold spot:
* calculates all denomination prices, saves them, records history, refreshes UI.
*/
const onGoldSpotPriceChanged = () => {
if (!goldbackEstimateEnabled || !goldbackEnabled) return;
const goldSpot = spotPrices && spotPrices.gold ? spotPrices.gold : 0;
if (!goldSpot || goldSpot <= 0) return;
const gbRate = computeGoldbackEstimatedRate(goldSpot);
const now = Date.now();
if (typeof GOLDBACK_DENOMINATIONS === 'undefined') return;
for (const d of GOLDBACK_DENOMINATIONS) {
const key = String(d.weight);
const denomPrice = Math.round(gbRate * d.weight * 100) / 100;
goldbackPrices[key] = { price: denomPrice, updatedAt: now };
}
if (typeof saveGoldbackPrices === 'function') saveGoldbackPrices();
if (typeof recordGoldbackPrices === 'function') recordGoldbackPrices();
// Refresh settings UI if the Goldback panel is visible
if (typeof syncGoldbackSettingsUI === 'function') syncGoldbackSettingsUI();
};
// =============================================================================
// GOLDBACK PRICE HISTORY MODAL
// =============================================================================
/** @type {string} Current filter text for the history table */
let gbHistoryFilterText = '';
/** @type {string} Current sort column */
let gbHistorySortColumn = '';
/** @type {boolean} Sort direction (true = ascending) */
let gbHistorySortAsc = true;
/**
* Flattens goldbackPriceHistory into a row array for table rendering.
* Each entry: { denomination, label, price, timestamp }
*/
const flattenGoldbackHistory = () => {
const rows = [];
const denomLabels = {};
if (typeof GOLDBACK_DENOMINATIONS !== 'undefined') {
for (const d of GOLDBACK_DENOMINATIONS) {
denomLabels[String(d.weight)] = d.label;
}
}
for (const [key, entries] of Object.entries(goldbackPriceHistory)) {
if (!Array.isArray(entries)) continue;
const label = denomLabels[key] || `${key} gb`;
for (const e of entries) {
rows.push({
denomination: key,
label,
price: e.price,
timestamp: e.ts,
timeStr: typeof formatTimestamp === 'function' ? formatTimestamp(e.ts) : new Date(e.ts).toLocaleString(),
});
}
}
return rows;
};
/**
* Renders the Goldback history table with filtering and sorting.
*/
const renderGoldbackHistoryTable = () => {
const table = document.getElementById('goldbackHistoryTable');
if (!table) return;
let data = flattenGoldbackHistory();
// Filter
if (gbHistoryFilterText) {
const f = gbHistoryFilterText.toLowerCase();
data = data.filter(e =>
Object.values(e).some(v => String(v).toLowerCase().includes(f))
);
}
// Sort
if (gbHistorySortColumn) {
data.sort((a, b) => {
const valA = a[gbHistorySortColumn];
const valB = b[gbHistorySortColumn];
if (valA < valB) return gbHistorySortAsc ? -1 : 1;
if (valA > valB) return gbHistorySortAsc ? 1 : -1;
return 0;
});
} else {
// Default: newest first
data.sort((a, b) => b.timestamp - a.timestamp);
}
let html = '<tr><th data-column="timestamp">Time</th><th data-column="label">Denomination</th><th data-column="price">Price</th></tr>';
for (const e of data) {
html += `<tr><td>${e.timeStr}</td><td>${e.label}</td><td>${typeof formatCurrency === 'function' ? formatCurrency(e.price) : '$' + e.price.toFixed(2)}</td></tr>`;
}
// nosemgrep: javascript.browser.security.insecure-innerhtml.insecure-innerhtml, javascript.browser.security.insecure-document-method.insecure-document-method
table.innerHTML = html;
// Click-to-sort headers
table.querySelectorAll('th').forEach(th => {
th.style.cursor = 'pointer';
th.addEventListener('click', () => {
const col = th.dataset.column;
if (gbHistorySortColumn === col) {
gbHistorySortAsc = !gbHistorySortAsc;
} else {
gbHistorySortColumn = col;
gbHistorySortAsc = true;
}
renderGoldbackHistoryTable();
});
});
};
/**
* Shows the Goldback price history modal.
*/
const showGoldbackHistoryModal = () => {
const modal = document.getElementById('goldbackHistoryModal');
if (!modal) return;
gbHistorySortColumn = '';
gbHistorySortAsc = true;
gbHistoryFilterText = '';
const filterInput = document.getElementById('goldbackHistoryFilter');
const clearFilterBtn = document.getElementById('goldbackHistoryClearFilterBtn');
if (filterInput) {
filterInput.value = '';
filterInput.oninput = (e) => {
gbHistoryFilterText = e.target.value;
renderGoldbackHistoryTable();
};
}
if (clearFilterBtn) {
clearFilterBtn.onclick = () => {
gbHistoryFilterText = '';
if (filterInput) filterInput.value = '';
renderGoldbackHistoryTable();
};
}
renderGoldbackHistoryTable();
if (typeof openModalById === 'function') {
openModalById('goldbackHistoryModal');
} else {
modal.style.display = 'flex';
}
};
/**
* Hides the Goldback price history modal.
*/
const hideGoldbackHistoryModal = () => {
if (typeof closeModalById === 'function') {
closeModalById('goldbackHistoryModal');
} else {
const modal = document.getElementById('goldbackHistoryModal');
if (modal) modal.style.display = 'none';
}
};
/**
* Exports Goldback price history as CSV.
*/
const exportGoldbackHistory = () => {
const data = flattenGoldbackHistory();
if (data.length === 0) {
alert('No Goldback price history to export.');
return;
}
// Sort newest first
data.sort((a, b) => b.timestamp - a.timestamp);
const csvLines = ['Time,Denomination,Price'];
for (const e of data) {
csvLines.push(`"${e.timeStr}","${e.label}","${e.price.toFixed(2)}"`);
}
const blob = new Blob([csvLines.join('\n')], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `goldback-history-${new Date().toISOString().slice(0, 10)}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// =============================================================================
// GLOBAL EXPOSURE
// =============================================================================
if (typeof window !== 'undefined') {
window.loadGoldbackEstimateEnabled = loadGoldbackEstimateEnabled;
window.saveGoldbackEstimateEnabled = saveGoldbackEstimateEnabled;
window.loadGoldbackEstimateModifier = loadGoldbackEstimateModifier;
window.saveGoldbackEstimateModifier = saveGoldbackEstimateModifier;
window.computeGoldbackEstimatedRate = computeGoldbackEstimatedRate;
window.onGoldSpotPriceChanged = onGoldSpotPriceChanged;
window.saveGoldbackPrices = saveGoldbackPrices;
window.loadGoldbackPrices = loadGoldbackPrices;
window.saveGoldbackPriceHistory = saveGoldbackPriceHistory;
window.loadGoldbackPriceHistory = loadGoldbackPriceHistory;
window.loadGoldbackEnabled = loadGoldbackEnabled;
window.saveGoldbackEnabled = saveGoldbackEnabled;
window.recordGoldbackPrices = recordGoldbackPrices;
window.getGoldbackDenominationPrice = getGoldbackDenominationPrice;
window.isGoldbackPricingActive = isGoldbackPricingActive;
window.showGoldbackHistoryModal = showGoldbackHistoryModal;
window.hideGoldbackHistoryModal = hideGoldbackHistoryModal;
window.exportGoldbackHistory = exportGoldbackHistory;
}