ysdcsad
All checks were successful
Deploy / deploy-staging (push) Successful in 33s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-08-22 00:44:30 +02:00
parent c17566a7d2
commit af0d4fd428
2 changed files with 45 additions and 112 deletions

View File

@@ -1254,7 +1254,6 @@
).toUpperCase();
return ledgerCurrency || currentMiningCurrency || currentSettings.crypto_currency || 'DOGE';
})();
const currentWalletMiningBalance = walletPurchasingPower(latestWalletSnapshot, currentMiningCurrency);
const preferredCurrencyCodes = Array.isArray(currentSettings.preferred_currencies)
? currentSettings.preferred_currencies.map((code) => String(code || '').toUpperCase()).filter(Boolean)
: [];
@@ -1305,14 +1304,6 @@
return false;
}
if (
selectedOfferType === 'crypto' &&
!Number.isFinite(maxDogeFilter) &&
!isOfferAvailableForWallet(offer, latestWalletSnapshot)
) {
return false;
}
if (selectedOfferType === 'fiat' && !Number.isFinite(speedMin)) {
return false;
}
@@ -1331,7 +1322,18 @@
return true;
});
const scenarioMinerOffers = filteredMinerOffers;
const pinnedReferenceMinerOffer = evaluatedMinerOffers.find((offer) => (
String(offer.payment_type || '').toLowerCase() === 'crypto'
&& Number(offer.runtime_months) === 36
&& Math.abs(toKhPerSecond(offer.mining_speed_value, offer.mining_speed_unit) - 50) < 0.0001
)) || null;
const visibleMinerOffers = pinnedReferenceMinerOffer && !filteredMinerOffers.some((offer) => String(offer.id) === String(pinnedReferenceMinerOffer.id))
? [{ ...pinnedReferenceMinerOffer, is_pinned_reference: true }].concat(filteredMinerOffers)
: filteredMinerOffers.map((offer) => ({
...offer,
is_pinned_reference: pinnedReferenceMinerOffer !== null && String(offer.id) === String(pinnedReferenceMinerOffer.id),
}));
const scenarioMinerOffers = visibleMinerOffers;
const preferredMinerOffer = currentSummaryMinerOffers
.filter((offer) => String(offer.payment_type || '').toLowerCase() === 'crypto')
.map((offer) => {
@@ -1367,13 +1369,20 @@
offer_hashrate_mh: targetOfferHashrateKh / 1000,
};
})();
const resolvedPreferredMinerOffer = preferredMinerOffer || syntheticPreferredMinerOffer;
const resolvedPreferredMinerOffer = pinnedReferenceMinerOffer || preferredMinerOffer || syntheticPreferredMinerOffer;
const preferredOfferRequiredCoins = resolvedPreferredMinerOffer
? convertCurrencyValue(
resolvedPreferredMinerOffer.effective_price_amount ?? resolvedPreferredMinerOffer.crypto_display_price_amount ?? resolvedPreferredMinerOffer.usd_display_price_amount,
resolvedPreferredMinerOffer.effective_price_currency || resolvedPreferredMinerOffer.crypto_display_price_currency || resolvedPreferredMinerOffer.usd_display_price_currency || currentMiningCurrency,
currentMiningCurrency
)
? (() => {
const directCryptoAmount = Number(resolvedPreferredMinerOffer.crypto_display_price_amount);
const directCryptoCurrency = String(resolvedPreferredMinerOffer.crypto_display_price_currency || '').toUpperCase();
if (Number.isFinite(directCryptoAmount) && directCryptoAmount > 0 && directCryptoCurrency === currentMiningCurrency) {
return directCryptoAmount;
}
return convertCurrencyValue(
resolvedPreferredMinerOffer.effective_price_amount ?? resolvedPreferredMinerOffer.crypto_display_price_amount ?? resolvedPreferredMinerOffer.usd_display_price_amount,
resolvedPreferredMinerOffer.effective_price_currency || resolvedPreferredMinerOffer.crypto_display_price_currency || resolvedPreferredMinerOffer.usd_display_price_currency || currentMiningCurrency,
currentMiningCurrency
);
})()
: null;
const preferredOfferCurrentCoins = payload?.summary?.payouts?.holdings_current_asset !== null
&& payload?.summary?.payouts?.holdings_current_asset !== undefined
@@ -1765,91 +1774,6 @@
return rate === null ? null : numericValue * rate;
}
function walletAssetBalance(snapshot, currency) {
const code = String(currency || '').toUpperCase();
if (!snapshot || !code) {
return null;
}
const assets = snapshot.balances_json && typeof snapshot.balances_json === 'object' ? snapshot.balances_json : {};
const asset = assets[code] ?? assets[code.toLowerCase()] ?? assets[code.toUpperCase()];
const snapshotCurrency = String(snapshot.wallet_currency || '').toUpperCase();
const rawBalance = asset && typeof asset === 'object'
? asset.balance
: (asset ?? (snapshotCurrency === code ? snapshot.wallet_balance : null));
const balance = Number(rawBalance);
return Number.isFinite(balance) ? balance : null;
}
function walletPurchasingPower(snapshot, targetCurrency) {
const target = String(targetCurrency || '').toUpperCase();
if (!snapshot || !target) {
return null;
}
const directBalance = walletAssetBalance(snapshot, target);
if (Number.isFinite(directBalance)) {
return directBalance;
}
const totalValueAmount = Number(snapshot.total_value_amount);
const totalValueCurrency = String(snapshot.total_value_currency || '').toUpperCase();
if (Number.isFinite(totalValueAmount) && totalValueCurrency) {
const convertedTotal = convertCurrencyValue(totalValueAmount, totalValueCurrency, target);
if (Number.isFinite(convertedTotal)) {
return convertedTotal;
}
}
const assets = snapshot.balances_json && typeof snapshot.balances_json === 'object' ? snapshot.balances_json : {};
let total = 0;
let matched = false;
for (const [assetCode, assetValue] of Object.entries(assets)) {
const normalizedCode = String(assetCode || '').toUpperCase();
const balance = Number(assetValue && typeof assetValue === 'object' ? assetValue.balance : assetValue);
const priceAmount = Number(assetValue && typeof assetValue === 'object' ? assetValue.price_amount : null);
const priceCurrency = String(assetValue && typeof assetValue === 'object' ? assetValue.price_currency : '').toUpperCase();
if (!normalizedCode || !Number.isFinite(balance) || balance <= 0) {
continue;
}
if (normalizedCode === target) {
total += balance;
matched = true;
continue;
}
if (Number.isFinite(priceAmount) && priceAmount > 0 && priceCurrency) {
const assetValueInPriceCurrency = balance * priceAmount;
const converted = convertCurrencyValue(assetValueInPriceCurrency, priceCurrency, target);
if (Number.isFinite(converted)) {
total += converted;
matched = true;
}
}
}
return matched ? total : null;
}
function isOfferAvailableForWallet(offer, walletSnapshot) {
if (!offer || !offer.is_active) {
return false;
}
if (String(offer.payment_type || '').toLowerCase() !== 'crypto') {
return true;
}
const currency = String(offer.effective_price_currency || offer.price_currency || '').toUpperCase();
const price = Number(offer.effective_price_amount);
const balance = Number(walletPurchasingPower(walletSnapshot, currency));
if (!currency || !Number.isFinite(price) || !Number.isFinite(balance)) {
return true;
}
return price <= balance + 0.00000001;
}
function latestFxHistoryRate(fromCurrency, toCurrency) {
const from = String(fromCurrency || '').toUpperCase();
const to = String(toCurrency || '').toUpperCase();
@@ -2175,9 +2099,12 @@
}
}
const effectivePrice = Number(row.effective_price_per_coin);
const convertedPrice = Number.isFinite(effectivePrice)
? convertMeasurementMoney(row, effectivePrice, reportCurrency)
const rawPrice = row.effective_price_per_coin !== null && row.effective_price_per_coin !== undefined
? row.effective_price_per_coin
: row.price_per_coin;
const sourcePrice = Number(rawPrice);
const convertedPrice = Number.isFinite(sourcePrice) && sourcePrice > 0
? convertMeasurementMoney(row, sourcePrice, reportCurrency)
: null;
if (!dayKey || !Number.isFinite(Number(convertedPrice))) {
return;
@@ -3853,9 +3780,7 @@
renderMinerTable(inactiveMinerRows, 'Noch keine inaktiven Miner hinterlegt.'),
]),
panel('Miner-Angebote', selectedOfferType === 'crypto'
? (currentWalletMiningBalance !== null
? `Crypto-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt und nur Angebote gezeigt, die dein NC-Wallet aktuell tragen kann (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency} Gegenwert).`
: `Crypto-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.`)
? `Crypto-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt. Der 50-kH/s-Server mit 36 Monaten bleibt als Referenz immer sichtbar; der Wallet-Bestand begrenzt weder Anzeige noch Mieten.`
: `Fiat-Angebote sind aktiv. Standardmäßig werden Laufzeiten ab ${fmtNumber(minOfferRuntimeMonths, 0)} Monaten berücksichtigt. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.`,
[
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
@@ -3917,9 +3842,14 @@
h('table', { key: 'table', className: 'mc-table' }, [
h('thead', { key: 'head' }, h('tr', null, ['Label', 'Hashrate', 'Preis', 'Erwartet/Tag', 'Break-even', 'Empfehlung', 'Aktion'].map((label) => h('th', { key: label }, label)))),
h('tbody', { key: 'body' },
filteredMinerOffers.length
? filteredMinerOffers.map((offer) => h('tr', { key: offer.id }, [
h('td', { key: 'label' }, offer.label),
visibleMinerOffers.length
? visibleMinerOffers.map((offer) => h('tr', { key: offer.id }, [
h('td', { key: 'label' }, [
h('div', { key: 'name' }, offer.label),
offer.is_pinned_reference
? h('div', { key: 'reference', className: 'mc-kicker' }, 'Referenzangebot · 50 kH/s · 36 Monate')
: null,
]),
h('td', { key: 'hashrate' }, [
h('div', { key: 'base' }, formatSpeed(offer.mining_speed_value, offer.mining_speed_unit, 'Basis') || 'n/a'),
Number(offer.bonus_speed_value) > 0
@@ -3982,7 +3912,6 @@
? h('div', { key: 'rec-ref', className: 'mc-kicker' }, `Basis ${fmtNumber(offer.base_price_amount, 6)} ${offer.base_price_currency}`)
: null,
offer.payment_type ? h('div', { key: 'paytype', className: 'mc-kicker' }, offer.payment_type === 'crypto' ? `Zahlung in Krypto (${currentSettings.crypto_currency || 'DOGE'})` : `Zahlung in FIAT (${offer.base_price_currency || 'EUR'})`) : null,
h('div', { key: 'availability', className: 'mc-kicker' }, isOfferAvailableForWallet(offer, latestWalletSnapshot) ? 'Im NC Wallet verfuegbar' : 'Nicht genug NC Wallet Bestand'),
h('div', { key: 'renew', className: 'mc-kicker' }, offer.auto_renew ? 'Automatische Verlängerung' : 'Laeuft aus'),
]),
h('td', { key: 'action' }, [
@@ -4030,7 +3959,7 @@
});
setPurchaseMinerModalOpen(true);
},
disabled: !isOfferAvailableForWallet(offer, latestWalletSnapshot),
disabled: saving,
}, 'Mieten'),
]),
]))

View File

@@ -6,6 +6,8 @@ Das Modul erfasst Mining-Messpunkte der aktuell konfigurierten Kryptowaehrung, a
Im Startseiten-Ueberblick zeigt die Kachel `DOGE Bestand` den direkten Wallet-Bestand, den nach Transfers bereinigten Miner-Bestand, deren Summe sowie den letzten Coin/USD-Kurs. Bei einer geaenderten Mining-Waehrung werden Beschriftung und Werte entsprechend dieser Waehrung gefuehrt.
Der Kursverlauf verwendet pro Upload dessen gespeicherten Kurs und zugeordnete historische FX-Referenz. Uploads ohne Kurs werden nicht als Kurs `0` dargestellt.
## Ordnerstruktur
```text
@@ -120,6 +122,8 @@ Fuer Auswertungen, Berichte und Listen speichert der Mining-Checker pro Messpunk
Die Angebotsberechnung fuer Krypto-Miner nutzt eine konfigurierbare Basis fuer `50 kH/s` und `3 Monate`. Standard ist `5.49 USD`, kann aber in den Modul-Settings angepasst werden.
Unabhaengig von aktiven Angebots-, Wallet-, Preis- oder Laufzeitfiltern bleibt der Krypto-Server mit `50 kH/s` und `36 Monaten` als Referenzangebot in der Miner-Angebotsliste sichtbar. Die Zielminer-Kachel auf der Startseite verwendet denselben Angebotsdatensatz und dessen direkt berechneten Krypto-Preis. Der Walletbestand begrenzt weder Angebotsanzeige noch die Aktion `Mieten`, weil der Mietbetrag vor dem Speichern manuell angepasst werden kann.
Zusaetzlich koennen in den Settings diese Leitwerte hinterlegt werden:
- minimale gewuenschte Mietlaufzeit in Monaten