miner
This commit is contained in:
@@ -904,6 +904,7 @@
|
|||||||
const [targetModalOpen, setTargetModalOpen] = useState(false);
|
const [targetModalOpen, setTargetModalOpen] = useState(false);
|
||||||
const [selectedMinerScenarioId, setSelectedMinerScenarioId] = useState(null);
|
const [selectedMinerScenarioId, setSelectedMinerScenarioId] = useState(null);
|
||||||
const [minerOfferFilters, setMinerOfferFilters] = useState({
|
const [minerOfferFilters, setMinerOfferFilters] = useState({
|
||||||
|
offer_type: 'crypto',
|
||||||
speed_min: '',
|
speed_min: '',
|
||||||
speed_unit: 'auto',
|
speed_unit: 'auto',
|
||||||
price_max: '',
|
price_max: '',
|
||||||
@@ -944,11 +945,9 @@
|
|||||||
const [walletWithdrawalRows, setWalletWithdrawalRows] = useState([]);
|
const [walletWithdrawalRows, setWalletWithdrawalRows] = useState([]);
|
||||||
const [minerOfferForm, setMinerOfferForm] = useState({
|
const [minerOfferForm, setMinerOfferForm] = useState({
|
||||||
label: '',
|
label: '',
|
||||||
runtime_months: '',
|
|
||||||
bonus_percent: '',
|
|
||||||
base_price_amount: '',
|
base_price_amount: '',
|
||||||
base_price_currency: 'USD',
|
base_price_currency: 'USD',
|
||||||
payment_type: 'fiat',
|
payment_type: 'crypto',
|
||||||
auto_renew: false,
|
auto_renew: false,
|
||||||
note: '',
|
note: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -1036,8 +1035,9 @@
|
|||||||
: currencies;
|
: currencies;
|
||||||
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
||||||
const evaluatedMinerOffers = Array.isArray(payload?.summary?.miner_offers) ? payload.summary.miner_offers : [];
|
const evaluatedMinerOffers = Array.isArray(payload?.summary?.miner_offers) ? payload.summary.miner_offers : [];
|
||||||
const availableMinerOffers = evaluatedMinerOffers.filter((offer) => isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance));
|
const selectedOfferType = String(minerOfferFilters.offer_type || 'crypto').toLowerCase() === 'fiat' ? 'fiat' : 'crypto';
|
||||||
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
||||||
|
const paymentType = String(offer.payment_type || '').toLowerCase();
|
||||||
const speedMin = minerOfferFilters.speed_min === '' ? null : Number(minerOfferFilters.speed_min);
|
const speedMin = minerOfferFilters.speed_min === '' ? null : Number(minerOfferFilters.speed_min);
|
||||||
const speedUnit = String(minerOfferFilters.speed_unit || 'auto');
|
const speedUnit = String(minerOfferFilters.speed_unit || 'auto');
|
||||||
const priceMax = minerOfferFilters.price_max === '' ? null : Number(minerOfferFilters.price_max);
|
const priceMax = minerOfferFilters.price_max === '' ? null : Number(minerOfferFilters.price_max);
|
||||||
@@ -1053,6 +1053,18 @@
|
|||||||
? offerHashrate * 1000
|
? offerHashrate * 1000
|
||||||
: offerHashrate;
|
: offerHashrate;
|
||||||
|
|
||||||
|
if (paymentType !== selectedOfferType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedOfferType === 'crypto' && !isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedOfferType === 'fiat' && !Number.isFinite(speedMin)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (Number.isFinite(speedMin) && (!Number.isFinite(comparableHashrate) || comparableHashrate < speedMin)) {
|
if (Number.isFinite(speedMin) && (!Number.isFinite(comparableHashrate) || comparableHashrate < speedMin)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1067,6 +1079,7 @@
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
const scenarioMinerOffers = filteredMinerOffers.filter((offer) => isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance));
|
||||||
const speedUnits = ['kH/s', 'MH/s'];
|
const speedUnits = ['kH/s', 'MH/s'];
|
||||||
const fiatCurrencies = currencies.filter((currency) => !currency.is_crypto);
|
const fiatCurrencies = currencies.filter((currency) => !currency.is_crypto);
|
||||||
const cryptoCurrencies = currencies.filter((currency) => !!currency.is_crypto);
|
const cryptoCurrencies = currencies.filter((currency) => !!currency.is_crypto);
|
||||||
@@ -1078,7 +1091,7 @@
|
|||||||
: cryptoCurrencies;
|
: cryptoCurrencies;
|
||||||
const selectableFiatCurrencies = preferredSelectableFiatCurrencies.length ? preferredSelectableFiatCurrencies : fiatCurrencies;
|
const selectableFiatCurrencies = preferredSelectableFiatCurrencies.length ? preferredSelectableFiatCurrencies : fiatCurrencies;
|
||||||
const selectableCryptoCurrencies = preferredSelectableCryptoCurrencies.length ? preferredSelectableCryptoCurrencies : cryptoCurrencies;
|
const selectableCryptoCurrencies = preferredSelectableCryptoCurrencies.length ? preferredSelectableCryptoCurrencies : cryptoCurrencies;
|
||||||
const selectedMinerScenario = availableMinerOffers.find((offer) => String(offer.id) === String(selectedMinerScenarioId)) || null;
|
const selectedMinerScenario = scenarioMinerOffers.find((offer) => String(offer.id) === String(selectedMinerScenarioId)) || null;
|
||||||
const activeMinerRows = currentPurchasedMiners.map((miner) => ({
|
const activeMinerRows = currentPurchasedMiners.map((miner) => ({
|
||||||
id: `purchase-${miner.id}`,
|
id: `purchase-${miner.id}`,
|
||||||
source: 'miete',
|
source: 'miete',
|
||||||
@@ -1130,19 +1143,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = availableMinerOffers.some((offer) => String(offer.id) === String(selectedMinerScenarioId));
|
const exists = scenarioMinerOffers.some((offer) => String(offer.id) === String(selectedMinerScenarioId));
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
setSelectedMinerScenarioId(null);
|
setSelectedMinerScenarioId(null);
|
||||||
}
|
}
|
||||||
}, [availableMinerOffers, selectedMinerScenarioId]);
|
}, [scenarioMinerOffers, selectedMinerScenarioId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!purchaseMinerModalOpen) {
|
if (!purchaseMinerModalOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedOffer = availableMinerOffers.find((offer) => String(offer.id) === String(purchaseMinerForm.offer_id))
|
const selectedOffer = scenarioMinerOffers.find((offer) => String(purchaseMinerForm.offer_id) === String(offer.id))
|
||||||
|| availableMinerOffers[0]
|
|| scenarioMinerOffers[0]
|
||||||
|| null;
|
|| null;
|
||||||
if (!selectedOffer) {
|
if (!selectedOffer) {
|
||||||
return;
|
return;
|
||||||
@@ -1162,7 +1175,7 @@
|
|||||||
reference_price_currency: current.reference_price_currency || selectedOffer.reference_price_currency || '',
|
reference_price_currency: current.reference_price_currency || selectedOffer.reference_price_currency || '',
|
||||||
auto_renew: current.auto_renew || !!selectedOffer.auto_renew,
|
auto_renew: current.auto_renew || !!selectedOffer.auto_renew,
|
||||||
}));
|
}));
|
||||||
}, [purchaseMinerModalOpen, availableMinerOffers, purchaseMinerForm.offer_id, currentSettings.crypto_currency]);
|
}, [purchaseMinerModalOpen, scenarioMinerOffers, purchaseMinerForm.offer_id, currentSettings.crypto_currency]);
|
||||||
|
|
||||||
function measurementFxRate(measurementId, fromCurrency, toCurrency) {
|
function measurementFxRate(measurementId, fromCurrency, toCurrency) {
|
||||||
const from = String(fromCurrency || '').toUpperCase();
|
const from = String(fromCurrency || '').toUpperCase();
|
||||||
@@ -2104,7 +2117,9 @@
|
|||||||
try {
|
try {
|
||||||
const offerPayload = {
|
const offerPayload = {
|
||||||
...minerOfferForm,
|
...minerOfferForm,
|
||||||
base_price_currency: minerOfferForm.payment_type === 'crypto' ? 'USD' : minerOfferForm.base_price_currency,
|
base_price_currency: minerOfferForm.payment_type === 'crypto'
|
||||||
|
? (minerOfferForm.base_price_currency || 'USD')
|
||||||
|
: 'EUR',
|
||||||
};
|
};
|
||||||
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/miner-offers`, {
|
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/miner-offers`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -2114,11 +2129,9 @@
|
|||||||
setMessage('Miner-Angebot gespeichert.');
|
setMessage('Miner-Angebot gespeichert.');
|
||||||
setMinerOfferForm({
|
setMinerOfferForm({
|
||||||
label: '',
|
label: '',
|
||||||
runtime_months: '',
|
|
||||||
bonus_percent: '',
|
|
||||||
base_price_amount: '',
|
base_price_amount: '',
|
||||||
base_price_currency: 'USD',
|
base_price_currency: 'USD',
|
||||||
payment_type: 'fiat',
|
payment_type: 'crypto',
|
||||||
auto_renew: false,
|
auto_renew: false,
|
||||||
note: '',
|
note: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -2173,7 +2186,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedOffer = availableMinerOffers.find((offer) => String(offer.id) === String(purchaseMinerForm.offer_id));
|
const selectedOffer = scenarioMinerOffers.find((offer) => String(offer.id) === String(purchaseMinerForm.offer_id));
|
||||||
if (!selectedOffer) {
|
if (!selectedOffer) {
|
||||||
setError('Bitte ein gueltiges Miner-Angebot auswaehlen.');
|
setError('Bitte ein gueltiges Miner-Angebot auswaehlen.');
|
||||||
return;
|
return;
|
||||||
@@ -2990,7 +3003,7 @@
|
|||||||
});
|
});
|
||||||
setPurchaseMinerModalOpen(true);
|
setPurchaseMinerModalOpen(true);
|
||||||
},
|
},
|
||||||
disabled: !availableMinerOffers.length,
|
disabled: !scenarioMinerOffers.length,
|
||||||
}, 'Neuen Miner mieten'),
|
}, 'Neuen Miner mieten'),
|
||||||
]),
|
]),
|
||||||
h('div', { key: 'list', className: 'mc-table-shell' }, [
|
h('div', { key: 'list', className: 'mc-table-shell' }, [
|
||||||
@@ -3036,9 +3049,11 @@
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
panel('Miner-Angebote', currentWalletMiningBalance !== null
|
panel('Miner-Angebote', selectedOfferType === 'crypto'
|
||||||
? `Hier werden nur Basis-Miner gepflegt. Die Verfuegbarkeit von Krypto-Angeboten wird gegen deinen aktuellen NC-Wallet-Bestand (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency}) bewertet.`
|
? (currentWalletMiningBalance !== null
|
||||||
: 'Hier werden nur Basis-Miner gepflegt. Alle vorgegebenen Geschwindigkeiten und Preise werden daraus automatisch abgeleitet.',
|
? `Crypto-Angebote sind aktiv. Angezeigt werden nur Angebote, die dein NC-Wallet aktuell tragen kann (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency}).`
|
||||||
|
: 'Crypto-Angebote sind aktiv. Solange kein Wallet-Bestand erkannt wird, bleiben die Krypto-Angebote sichtbar.')
|
||||||
|
: 'Fiat-Angebote sind aktiv. Die Berechnung startet erst, wenn du eine Geschwindigkeit eingibst.',
|
||||||
[
|
[
|
||||||
h('div', { key: 'actions', className: 'mc-inline-row' }, [
|
h('div', { key: 'actions', className: 'mc-inline-row' }, [
|
||||||
h('button', {
|
h('button', {
|
||||||
@@ -3049,7 +3064,11 @@
|
|||||||
}, 'Basis-Angebot anlegen'),
|
}, 'Basis-Angebot anlegen'),
|
||||||
]),
|
]),
|
||||||
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
||||||
inputField(`Min. Geschwindigkeit (${minerOfferFilters.speed_unit === 'kh' ? 'kH/s' : 'MH/s'})`, 'number', minerOfferFilters.speed_min, (value) => setMinerOfferFilters({ ...minerOfferFilters, speed_min: value }), '0.0001'),
|
selectField('Angebotsart', minerOfferFilters.offer_type, [
|
||||||
|
{ value: 'crypto', label: 'Crypto' },
|
||||||
|
{ value: 'fiat', label: 'Fiat' },
|
||||||
|
], (value) => setMinerOfferFilters({ ...minerOfferFilters, offer_type: value || 'crypto' })),
|
||||||
|
inputField(`${selectedOfferType === 'fiat' ? 'Geschwindigkeit' : 'Min. Geschwindigkeit'} (${minerOfferFilters.speed_unit === 'kh' ? 'kH/s' : 'MH/s'})`, 'number', minerOfferFilters.speed_min, (value) => setMinerOfferFilters({ ...minerOfferFilters, speed_min: value }), '0.0001'),
|
||||||
selectField('Geschwindigkeitseinheit', minerOfferFilters.speed_unit, [
|
selectField('Geschwindigkeitseinheit', minerOfferFilters.speed_unit, [
|
||||||
{ value: 'auto', label: 'MH/s' },
|
{ value: 'auto', label: 'MH/s' },
|
||||||
{ value: 'kh', label: 'kH/s' },
|
{ value: 'kh', label: 'kH/s' },
|
||||||
@@ -3140,7 +3159,7 @@
|
|||||||
}, 'Mieten'),
|
}, 'Mieten'),
|
||||||
]),
|
]),
|
||||||
]))
|
]))
|
||||||
: [h('tr', { key: 'empty' }, h('td', { colSpan: 7 }, 'Keine Angebote passen auf die gesetzten Filter.'))]
|
: [h('tr', { key: 'empty' }, h('td', { colSpan: 7 }, selectedOfferType === 'fiat' && minerOfferFilters.speed_min === '' ? 'Bitte zuerst eine Geschwindigkeit eingeben, damit Fiat-Angebote berechnet werden.' : 'Keine Angebote passen auf die gesetzten Filter.'))]
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@@ -3301,20 +3320,20 @@
|
|||||||
minerOfferModalOpen ? renderModal('Basis-Miner-Angebot anlegen', [
|
minerOfferModalOpen ? renderModal('Basis-Miner-Angebot anlegen', [
|
||||||
h('form', { key: 'form', className: 'mc-form', onSubmit: submitMinerOffer }, [
|
h('form', { key: 'form', className: 'mc-form', onSubmit: submitMinerOffer }, [
|
||||||
inputField('Label', 'text', minerOfferForm.label, (value) => setMinerOfferForm({ ...minerOfferForm, label: value })),
|
inputField('Label', 'text', minerOfferForm.label, (value) => setMinerOfferForm({ ...minerOfferForm, label: value })),
|
||||||
inputField('Laufzeit in Monaten', 'number', minerOfferForm.runtime_months, (value) => setMinerOfferForm({ ...minerOfferForm, runtime_months: value })),
|
|
||||||
displayField('Basis-Geschwindigkeit', baseOfferSpeedLabel(minerOfferForm.payment_type)),
|
displayField('Basis-Geschwindigkeit', baseOfferSpeedLabel(minerOfferForm.payment_type)),
|
||||||
inputField('Bonus-Hashrate in %', 'number', minerOfferForm.bonus_percent, (value) => setMinerOfferForm({ ...minerOfferForm, bonus_percent: value }), '0.01'),
|
displayField('Basis-Laufzeit', '3 Monate'),
|
||||||
inputField(minerOfferForm.payment_type === 'crypto' ? 'Basispreis in USD' : 'Basispreis', 'number', minerOfferForm.base_price_amount, (value) => setMinerOfferForm({ ...minerOfferForm, base_price_amount: value }), '0.000001'),
|
displayField('Ableitung', baseOfferDerivationLabel(minerOfferForm.payment_type)),
|
||||||
|
inputField(baseOfferPriceLabel(minerOfferForm.payment_type, currentSettings.crypto_currency), 'number', minerOfferForm.base_price_amount, (value) => setMinerOfferForm({ ...minerOfferForm, base_price_amount: value }), '0.000001'),
|
||||||
selectField('Zahlungsart', minerOfferForm.payment_type, [{ value: 'fiat', label: 'FIAT' }, { value: 'crypto', label: 'Krypto' }], (value) => setMinerOfferForm({
|
selectField('Zahlungsart', minerOfferForm.payment_type, [{ value: 'fiat', label: 'FIAT' }, { value: 'crypto', label: 'Krypto' }], (value) => setMinerOfferForm({
|
||||||
...minerOfferForm,
|
...minerOfferForm,
|
||||||
payment_type: value,
|
payment_type: value,
|
||||||
base_price_currency: value === 'crypto'
|
base_price_currency: value === 'crypto'
|
||||||
? 'USD'
|
? 'USD'
|
||||||
: (selectableFiatCurrencies[0]?.code || 'USD'),
|
: 'EUR',
|
||||||
})),
|
})),
|
||||||
minerOfferForm.payment_type === 'crypto'
|
minerOfferForm.payment_type === 'crypto'
|
||||||
? displayField('Kostenwaehrung', 'USD')
|
? selectField('Basiswaehrung', minerOfferForm.base_price_currency, Array.from(new Set(['USD', String(currentSettings.crypto_currency || 'DOGE').toUpperCase()])), (value) => setMinerOfferForm({ ...minerOfferForm, base_price_currency: value || 'USD' }))
|
||||||
: selectField('Basiswährung', minerOfferForm.base_price_currency, selectableFiatCurrencies.map((currency) => currency.code), (value) => setMinerOfferForm({ ...minerOfferForm, base_price_currency: value })),
|
: displayField('Basiswaehrung', 'EUR'),
|
||||||
h('label', { className: 'mc-checkbox' }, [
|
h('label', { className: 'mc-checkbox' }, [
|
||||||
h('input', { type: 'checkbox', checked: !!minerOfferForm.auto_renew, onChange: (event) => setMinerOfferForm({ ...minerOfferForm, auto_renew: event.target.checked }) }),
|
h('input', { type: 'checkbox', checked: !!minerOfferForm.auto_renew, onChange: (event) => setMinerOfferForm({ ...minerOfferForm, auto_renew: event.target.checked }) }),
|
||||||
'Automatische Verlängerung',
|
'Automatische Verlängerung',
|
||||||
@@ -3332,11 +3351,11 @@
|
|||||||
], () => setMinerOfferModalOpen(false)) : null,
|
], () => setMinerOfferModalOpen(false)) : null,
|
||||||
purchaseMinerModalOpen ? renderModal('Neuen Miner mieten', [
|
purchaseMinerModalOpen ? renderModal('Neuen Miner mieten', [
|
||||||
h('form', { key: 'form', className: 'mc-form', onSubmit: submitPurchaseMiner }, [
|
h('form', { key: 'form', className: 'mc-form', onSubmit: submitPurchaseMiner }, [
|
||||||
selectField('Angebot', purchaseMinerForm.offer_id, [{ value: '', label: 'Bitte waehlen' }].concat(availableMinerOffers.map((offer) => ({
|
selectField('Angebot', purchaseMinerForm.offer_id, [{ value: '', label: 'Bitte waehlen' }].concat(scenarioMinerOffers.map((offer) => ({
|
||||||
value: String(offer.id),
|
value: String(offer.id),
|
||||||
label: `${offer.label} · ${fmtNumber(offer.effective_price_amount, 6)} ${offer.effective_price_currency}`,
|
label: `${offer.label} · ${fmtNumber(offer.effective_price_amount, 6)} ${offer.effective_price_currency}`,
|
||||||
}))), (value) => {
|
}))), (value) => {
|
||||||
const offer = availableMinerOffers.find((item) => String(item.id) === String(value));
|
const offer = scenarioMinerOffers.find((item) => String(item.id) === String(value));
|
||||||
setPurchaseMinerForm({
|
setPurchaseMinerForm({
|
||||||
offer_id: value,
|
offer_id: value,
|
||||||
base_offer_id: offer ? String(offer.base_offer_id || offer.id || '') : '',
|
base_offer_id: offer ? String(offer.base_offer_id || offer.id || '') : '',
|
||||||
@@ -3666,7 +3685,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function baseOfferSpeedLabel(paymentType) {
|
function baseOfferSpeedLabel(paymentType) {
|
||||||
return String(paymentType || 'fiat') === 'crypto' ? '75 kH/s' : '50 kH/s';
|
return String(paymentType || 'fiat') === 'crypto' ? '50 kH/s' : '100 kH/s';
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseOfferPriceLabel(paymentType, cryptoCurrency) {
|
||||||
|
return String(paymentType || 'fiat') === 'crypto'
|
||||||
|
? `Basispreis fuer 50 kH/s auf 3 Monate in USD oder ${String(cryptoCurrency || 'DOGE').toUpperCase()}`
|
||||||
|
: 'Basispreis fuer 100 kH/s auf 3 Monate in EUR';
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseOfferDerivationLabel(paymentType) {
|
||||||
|
return String(paymentType || 'fiat') === 'crypto'
|
||||||
|
? 'Varianten fuer 3, 6, 12, 18, 24 und 36 Monate mit festen Rabatten und Bonuswerten'
|
||||||
|
: 'Varianten fuer 3, 6 und 12 Monate mit festen Rabatten und Bonuswerten';
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatHashrateWithBonus(speedValue, speedUnit, bonusValue, bonusUnit) {
|
function formatHashrateWithBonus(speedValue, speedUnit, bonusValue, bonusUnit) {
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ final class Router
|
|||||||
private const OVERVIEW_WINDOW_DAYS = 15;
|
private const OVERVIEW_WINDOW_DAYS = 15;
|
||||||
private const FX_FETCH_MAX_AGE_HOURS = 3.0;
|
private const FX_FETCH_MAX_AGE_HOURS = 3.0;
|
||||||
private const BASE_OFFER_SPEEDS = [
|
private const BASE_OFFER_SPEEDS = [
|
||||||
'fiat' => ['value' => 50.0, 'unit' => 'kH/s'],
|
'fiat' => ['value' => 100.0, 'unit' => 'kH/s'],
|
||||||
'crypto' => ['value' => 75.0, 'unit' => 'kH/s'],
|
'crypto' => ['value' => 50.0, 'unit' => 'kH/s'],
|
||||||
];
|
];
|
||||||
|
|
||||||
private string $moduleBasePath;
|
private string $moduleBasePath;
|
||||||
@@ -1907,18 +1907,23 @@ final class Router
|
|||||||
{
|
{
|
||||||
$paymentType = $this->enumValue($input['payment_type'] ?? 'fiat', ['fiat', 'crypto'], 'payment_type');
|
$paymentType = $this->enumValue($input['payment_type'] ?? 'fiat', ['fiat', 'crypto'], 'payment_type');
|
||||||
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
||||||
$bonusPercent = $this->optionalDecimal($input['bonus_percent'] ?? null);
|
$projectCryptoCurrency = $this->requiredCurrency($this->settings($projectKey, [
|
||||||
$basePriceCurrency = $paymentType === 'crypto'
|
'cost_plans' => false,
|
||||||
? 'USD'
|
'currencies' => false,
|
||||||
: $this->requiredCurrency($input['base_price_currency'] ?? ($input['reference_price_currency'] ?? $input['price_currency'] ?? null), 'base_price_currency');
|
'payouts' => false,
|
||||||
|
'wallet_withdrawals' => false,
|
||||||
|
'miner_offers' => false,
|
||||||
|
'purchased_miners' => false,
|
||||||
|
])['crypto_currency'] ?? 'DOGE', 'crypto_currency');
|
||||||
|
$basePriceCurrency = $this->requiredCurrency($input['base_price_currency'] ?? ($input['reference_price_currency'] ?? $input['price_currency'] ?? null), 'base_price_currency');
|
||||||
$payload = [
|
$payload = [
|
||||||
'label' => $this->requiredString($input['label'] ?? null, 'label', 120),
|
'label' => $this->requiredString($input['label'] ?? null, 'label', 120),
|
||||||
'runtime_months' => $this->optionalPositiveInt($input['runtime_months'] ?? null),
|
'runtime_months' => 3,
|
||||||
'mining_speed_value' => $baseSpeed['value'],
|
'mining_speed_value' => $baseSpeed['value'],
|
||||||
'mining_speed_unit' => $baseSpeed['unit'],
|
'mining_speed_unit' => $baseSpeed['unit'],
|
||||||
'bonus_speed_value' => $this->bonusSpeedFromPercent((float) $baseSpeed['value'], (string) $baseSpeed['unit'], $bonusPercent),
|
'bonus_speed_value' => null,
|
||||||
'bonus_speed_unit' => $bonusPercent !== null ? (string) $baseSpeed['unit'] : null,
|
'bonus_speed_unit' => null,
|
||||||
'bonus_percent' => $bonusPercent,
|
'bonus_percent' => null,
|
||||||
'base_price_amount' => $this->requiredDecimal($input['base_price_amount'] ?? ($input['reference_price_amount'] ?? $input['price_amount'] ?? null), 'base_price_amount'),
|
'base_price_amount' => $this->requiredDecimal($input['base_price_amount'] ?? ($input['reference_price_amount'] ?? $input['price_amount'] ?? null), 'base_price_amount'),
|
||||||
'base_price_currency' => $basePriceCurrency,
|
'base_price_currency' => $basePriceCurrency,
|
||||||
'payment_type' => $paymentType,
|
'payment_type' => $paymentType,
|
||||||
@@ -1927,12 +1932,23 @@ final class Router
|
|||||||
'is_active' => !empty($input['is_active']) ? 1 : 0,
|
'is_active' => !empty($input['is_active']) ? 1 : 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($bonusPercent !== null && $bonusPercent < 0) {
|
if ($paymentType === 'crypto') {
|
||||||
throw new ApiException('Bonus-Prozent darf nicht negativ sein.', 422);
|
$allowedCryptoCurrencies = array_values(array_unique(['USD', $projectCryptoCurrency]));
|
||||||
|
if (!in_array($payload['base_price_currency'], $allowedCryptoCurrencies, true)) {
|
||||||
|
throw new ApiException('Bei Krypto-Angeboten sind nur USD oder die Projekt-Kryptowaehrung erlaubt.', 422, [
|
||||||
|
'field' => 'base_price_currency',
|
||||||
|
'allowed' => $allowedCryptoCurrencies,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($payload['base_price_currency'] !== 'EUR') {
|
||||||
|
throw new ApiException('Bei FIAT-Angeboten ist aktuell nur EUR als Basiswaehrung erlaubt.', 422, [
|
||||||
|
'field' => 'base_price_currency',
|
||||||
|
'allowed' => ['EUR'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertCurrencyType($payload['base_price_currency'], false, 'base_price_currency');
|
|
||||||
|
|
||||||
return $this->repository()->saveMinerOffer($projectKey, $payload);
|
return $this->repository()->saveMinerOffer($projectKey, $payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,51 @@ use Modules\MiningChecker\Support\ApiException;
|
|||||||
final class AnalyticsService
|
final class AnalyticsService
|
||||||
{
|
{
|
||||||
private const BASE_OFFER_SPEEDS = [
|
private const BASE_OFFER_SPEEDS = [
|
||||||
'fiat' => ['value' => 50.0, 'unit' => 'kH/s'],
|
'fiat' => ['value' => 100.0, 'unit' => 'kH/s'],
|
||||||
'crypto' => ['value' => 75.0, 'unit' => 'kH/s'],
|
'crypto' => ['value' => 50.0, 'unit' => 'kH/s'],
|
||||||
];
|
];
|
||||||
|
|
||||||
private const OFFER_SPEED_MULTIPLIERS = [
|
private const OFFER_SPEED_OPTIONS = [
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
['value' => 50.0, 'unit' => 'kH/s'],
|
||||||
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
['value' => 75.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 100.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 150.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 200.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 250.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 300.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 350.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 400.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 450.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 500.0, 'unit' => 'kH/s'],
|
||||||
|
['value' => 1.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 5.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 10.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 15.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 20.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 30.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 50.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 75.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 100.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 150.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 200.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 250.0, 'unit' => 'MH/s'],
|
||||||
|
['value' => 300.0, 'unit' => 'MH/s'],
|
||||||
|
];
|
||||||
|
|
||||||
|
private const OFFER_RUNTIME_CONFIGS = [
|
||||||
|
'crypto' => [
|
||||||
|
['months' => 3, 'discount_percent' => 0.0, 'bonus_percent' => 5.0],
|
||||||
|
['months' => 6, 'discount_percent' => 10.0, 'bonus_percent' => 10.0],
|
||||||
|
['months' => 12, 'discount_percent' => 35.0, 'bonus_percent' => 10.0],
|
||||||
|
['months' => 18, 'discount_percent' => 45.0, 'bonus_percent' => 15.0],
|
||||||
|
['months' => 24, 'discount_percent' => 55.0, 'bonus_percent' => 20.0],
|
||||||
|
['months' => 36, 'discount_percent' => 65.0, 'bonus_percent' => 20.0],
|
||||||
|
],
|
||||||
|
'fiat' => [
|
||||||
|
['months' => 3, 'discount_percent' => 0.0, 'bonus_percent' => 5.0],
|
||||||
|
['months' => 6, 'discount_percent' => 10.0, 'bonus_percent' => 10.0],
|
||||||
|
['months' => 12, 'discount_percent' => 35.0, 'bonus_percent' => 10.0],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
private ?FxService $fx;
|
private ?FxService $fx;
|
||||||
@@ -970,7 +1008,6 @@ final class AnalyticsService
|
|||||||
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
$baseSpeed = self::BASE_OFFER_SPEEDS[$paymentType] ?? self::BASE_OFFER_SPEEDS['fiat'];
|
||||||
$baseSpeedValue = (float) $baseSpeed['value'];
|
$baseSpeedValue = (float) $baseSpeed['value'];
|
||||||
$baseSpeedUnit = (string) $baseSpeed['unit'];
|
$baseSpeedUnit = (string) $baseSpeed['unit'];
|
||||||
$baseBonusPercent = $this->deriveBonusPercent($offer, $baseSpeedValue, $baseSpeedUnit);
|
|
||||||
$basePriceAmount = is_numeric($offer['base_price_amount'] ?? null)
|
$basePriceAmount = is_numeric($offer['base_price_amount'] ?? null)
|
||||||
? (float) $offer['base_price_amount']
|
? (float) $offer['base_price_amount']
|
||||||
: (is_numeric($offer['reference_price_amount'] ?? null)
|
: (is_numeric($offer['reference_price_amount'] ?? null)
|
||||||
@@ -978,33 +1015,60 @@ final class AnalyticsService
|
|||||||
: (is_numeric($offer['usd_reference_amount'] ?? null)
|
: (is_numeric($offer['usd_reference_amount'] ?? null)
|
||||||
? (float) $offer['usd_reference_amount']
|
? (float) $offer['usd_reference_amount']
|
||||||
: (is_numeric($offer['price_amount'] ?? null) ? (float) $offer['price_amount'] : null)));
|
: (is_numeric($offer['price_amount'] ?? null) ? (float) $offer['price_amount'] : null)));
|
||||||
|
$baseSpeedMh = $this->normalizeHashrateMh($baseSpeedValue, $baseSpeedUnit);
|
||||||
|
$runtimeConfigs = self::OFFER_RUNTIME_CONFIGS[$paymentType] ?? self::OFFER_RUNTIME_CONFIGS['fiat'];
|
||||||
|
|
||||||
foreach (self::OFFER_SPEED_MULTIPLIERS as $multiplier) {
|
foreach ($runtimeConfigs as $runtimeConfig) {
|
||||||
$derivedSpeedValue = $baseSpeedValue * $multiplier;
|
$runtimeMonths = (int) ($runtimeConfig['months'] ?? 0);
|
||||||
$derivedBonusValue = $baseBonusPercent !== null
|
$discountPercent = (float) ($runtimeConfig['discount_percent'] ?? 0.0);
|
||||||
? round($derivedSpeedValue * ($baseBonusPercent / 100), 4)
|
$bonusPercent = (float) ($runtimeConfig['bonus_percent'] ?? 0.0);
|
||||||
: null;
|
if ($runtimeMonths <= 0 || $baseSpeedMh <= 0) {
|
||||||
$derivedPriceAmount = $basePriceAmount !== null
|
continue;
|
||||||
? round($basePriceAmount * $multiplier, 8)
|
}
|
||||||
: null;
|
|
||||||
|
|
||||||
$expanded[] = array_merge($offer, [
|
foreach (self::OFFER_SPEED_OPTIONS as $speedOption) {
|
||||||
'id' => sprintf('%s:%s:%s', (string) ($offer['id'] ?? 'offer'), $paymentType, rtrim(rtrim(number_format($derivedSpeedValue, 4, '.', ''), '0'), '.')),
|
$derivedSpeedValue = (float) ($speedOption['value'] ?? 0.0);
|
||||||
'base_offer_id' => $offer['id'] ?? null,
|
$derivedSpeedUnit = (string) ($speedOption['unit'] ?? '');
|
||||||
'base_offer_label' => $offer['label'] ?? null,
|
$derivedSpeedMh = $this->normalizeHashrateMh($derivedSpeedValue, $derivedSpeedUnit);
|
||||||
'payment_type' => $paymentType,
|
if ($derivedSpeedMh <= 0) {
|
||||||
'label' => trim(((string) ($offer['label'] ?? 'Miner-Angebot')) . ' · ' . $this->formatSpeedLabel($derivedSpeedValue, $baseSpeedUnit)),
|
continue;
|
||||||
'mining_speed_value' => $derivedSpeedValue,
|
}
|
||||||
'mining_speed_unit' => $baseSpeedUnit,
|
|
||||||
'bonus_speed_value' => $derivedBonusValue,
|
$speedFactor = $derivedSpeedMh / $baseSpeedMh;
|
||||||
'bonus_speed_unit' => $derivedBonusValue !== null ? $baseSpeedUnit : null,
|
$runtimeFactor = $runtimeMonths / 3.0;
|
||||||
'bonus_percent' => $baseBonusPercent,
|
$discountFactor = max(0.0, 1.0 - ($discountPercent / 100.0));
|
||||||
'speed_factor' => $multiplier,
|
$derivedBonusValue = round($derivedSpeedValue * ($bonusPercent / 100.0), 4);
|
||||||
'base_speed_value' => $baseSpeedValue,
|
$derivedPriceAmount = $basePriceAmount !== null
|
||||||
'base_speed_unit' => $baseSpeedUnit,
|
? round($basePriceAmount * $speedFactor * $runtimeFactor * $discountFactor, 8)
|
||||||
'base_price_amount' => $derivedPriceAmount,
|
: null;
|
||||||
'reference_price_amount' => $derivedPriceAmount,
|
|
||||||
]);
|
$expanded[] = array_merge($offer, [
|
||||||
|
'id' => sprintf(
|
||||||
|
'%s:%s:%s:%s',
|
||||||
|
(string) ($offer['id'] ?? 'offer'),
|
||||||
|
$paymentType,
|
||||||
|
$runtimeMonths,
|
||||||
|
rtrim(rtrim(number_format($derivedSpeedMh, 4, '.', ''), '0'), '.')
|
||||||
|
),
|
||||||
|
'base_offer_id' => $offer['id'] ?? null,
|
||||||
|
'base_offer_label' => $offer['label'] ?? null,
|
||||||
|
'payment_type' => $paymentType,
|
||||||
|
'label' => trim(((string) ($offer['label'] ?? 'Miner-Angebot')) . ' · ' . $this->formatSpeedLabel($derivedSpeedValue, $derivedSpeedUnit) . ' · ' . $runtimeMonths . ' Monate'),
|
||||||
|
'runtime_months' => $runtimeMonths,
|
||||||
|
'mining_speed_value' => $derivedSpeedValue,
|
||||||
|
'mining_speed_unit' => $derivedSpeedUnit,
|
||||||
|
'bonus_speed_value' => $derivedBonusValue > 0 ? $derivedBonusValue : null,
|
||||||
|
'bonus_speed_unit' => $derivedBonusValue > 0 ? $derivedSpeedUnit : null,
|
||||||
|
'bonus_percent' => $bonusPercent,
|
||||||
|
'discount_percent' => $discountPercent,
|
||||||
|
'speed_factor' => $speedFactor,
|
||||||
|
'base_speed_value' => $baseSpeedValue,
|
||||||
|
'base_speed_unit' => $baseSpeedUnit,
|
||||||
|
'base_runtime_months' => 3,
|
||||||
|
'base_price_amount' => $derivedPriceAmount,
|
||||||
|
'reference_price_amount' => $derivedPriceAmount,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1019,7 +1083,7 @@ final class AnalyticsService
|
|||||||
return $runtimeCompare;
|
return $runtimeCompare;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((int) ($left['speed_factor'] ?? 0)) <=> ((int) ($right['speed_factor'] ?? 0));
|
return ((float) ($left['speed_factor'] ?? 0.0)) <=> ((float) ($right['speed_factor'] ?? 0.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
return $expanded;
|
return $expanded;
|
||||||
|
|||||||
Reference in New Issue
Block a user