qewqwe
This commit is contained in:
@@ -477,9 +477,6 @@
|
|||||||
daily_cost_currency: 'EUR',
|
daily_cost_currency: 'EUR',
|
||||||
report_currency: 'EUR',
|
report_currency: 'EUR',
|
||||||
crypto_currency: 'DOGE',
|
crypto_currency: 'DOGE',
|
||||||
crypto_base_price_amount: '',
|
|
||||||
crypto_base_price_currency: 'USD',
|
|
||||||
fiat_base_price_amount: '',
|
|
||||||
preferred_currencies: ['DOGE', 'USD', 'EUR'],
|
preferred_currencies: ['DOGE', 'USD', 'EUR'],
|
||||||
cost_plans: [],
|
cost_plans: [],
|
||||||
currencies: [],
|
currencies: [],
|
||||||
@@ -890,6 +887,7 @@
|
|||||||
crypto_base_price_currency: 'USD',
|
crypto_base_price_currency: 'USD',
|
||||||
fiat_base_price_amount: '',
|
fiat_base_price_amount: '',
|
||||||
});
|
});
|
||||||
|
const [offerBasisHistory, setOfferBasisHistory] = useState([]);
|
||||||
const [previewMinerOffers, setPreviewMinerOffers] = useState([]);
|
const [previewMinerOffers, setPreviewMinerOffers] = useState([]);
|
||||||
const [previewOffersLoading, setPreviewOffersLoading] = useState(false);
|
const [previewOffersLoading, setPreviewOffersLoading] = useState(false);
|
||||||
const [moduleAuthForm, setModuleAuthForm] = useState({
|
const [moduleAuthForm, setModuleAuthForm] = useState({
|
||||||
@@ -1029,6 +1027,9 @@
|
|||||||
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies;
|
||||||
const evaluatedMinerOffers = Array.isArray(previewMinerOffers) ? previewMinerOffers : [];
|
const evaluatedMinerOffers = Array.isArray(previewMinerOffers) ? previewMinerOffers : [];
|
||||||
const selectedOfferType = String(minerOfferFilters.offer_type || 'crypto').toLowerCase() === 'fiat' ? 'fiat' : 'crypto';
|
const selectedOfferType = String(minerOfferFilters.offer_type || 'crypto').toLowerCase() === 'fiat' ? 'fiat' : 'crypto';
|
||||||
|
const visibleOfferBasisHistory = (Array.isArray(offerBasisHistory) ? offerBasisHistory : [])
|
||||||
|
.filter((entry) => String(entry.offer_type || '').toLowerCase() === selectedOfferType)
|
||||||
|
.slice(0, 6);
|
||||||
const hasCryptoOfferBasis = Number(offerPreviewForm.crypto_base_price_amount) > 0;
|
const hasCryptoOfferBasis = Number(offerPreviewForm.crypto_base_price_amount) > 0;
|
||||||
const hasFiatOfferBasis = Number(offerPreviewForm.fiat_base_price_amount) > 0;
|
const hasFiatOfferBasis = Number(offerPreviewForm.fiat_base_price_amount) > 0;
|
||||||
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => {
|
||||||
@@ -1188,7 +1189,7 @@
|
|||||||
} finally {
|
} finally {
|
||||||
setPreviewOffersLoading(false);
|
setPreviewOffersLoading(false);
|
||||||
}
|
}
|
||||||
}, 250);
|
}, selectedOfferType === 'crypto' ? 1000 : 250);
|
||||||
|
|
||||||
return () => window.clearTimeout(timeoutId);
|
return () => window.clearTimeout(timeoutId);
|
||||||
}, [
|
}, [
|
||||||
@@ -1532,8 +1533,24 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadFxHistory(projectKey);
|
loadFxHistory(projectKey);
|
||||||
|
loadOfferBasisHistory(projectKey);
|
||||||
}, [activeTab, projectKey]);
|
}, [activeTab, projectKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Array.isArray(offerBasisHistory) || offerBasisHistory.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestCrypto = offerBasisHistory.find((entry) => String(entry.offer_type || '').toLowerCase() === 'crypto') || null;
|
||||||
|
const latestFiat = offerBasisHistory.find((entry) => String(entry.offer_type || '').toLowerCase() === 'fiat') || null;
|
||||||
|
|
||||||
|
setOfferPreviewForm((current) => ({
|
||||||
|
crypto_base_price_amount: current.crypto_base_price_amount || (latestCrypto && latestCrypto.base_price_amount !== null && latestCrypto.base_price_amount !== undefined ? String(latestCrypto.base_price_amount) : ''),
|
||||||
|
crypto_base_price_currency: current.crypto_base_price_currency || latestCrypto?.base_price_currency || 'USD',
|
||||||
|
fiat_base_price_amount: current.fiat_base_price_amount || (latestFiat && latestFiat.base_price_amount !== null && latestFiat.base_price_amount !== undefined ? String(latestFiat.base_price_amount) : ''),
|
||||||
|
}));
|
||||||
|
}, [offerBasisHistory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab !== 'wallet') {
|
if (activeTab !== 'wallet') {
|
||||||
return;
|
return;
|
||||||
@@ -2398,6 +2415,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadOfferBasisHistory(key) {
|
||||||
|
try {
|
||||||
|
const result = await request(`${apiBase}/projects/${encodeURIComponent(key)}/offer-basis-history`, { timeoutMs: 6000 });
|
||||||
|
setOfferBasisHistory(Array.isArray(result) ? result : []);
|
||||||
|
} catch (err) {
|
||||||
|
setOfferBasisHistory([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rememberOfferBasisInput(offerType, amount, currency) {
|
||||||
|
const numericAmount = Number(amount);
|
||||||
|
if (!Number.isFinite(numericAmount) || numericAmount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/offer-basis-history`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
offer_type: offerType,
|
||||||
|
base_price_amount: numericAmount,
|
||||||
|
base_price_currency: offerType === 'crypto' ? (currency || 'USD') : 'EUR',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await loadOfferBasisHistory(projectKey);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetReportCurrencyOverride() {
|
function resetReportCurrencyOverride() {
|
||||||
setReportCurrencyOverride('');
|
setReportCurrencyOverride('');
|
||||||
setCookie('mining_checker_report_currency', '', 0);
|
setCookie('mining_checker_report_currency', '', 0);
|
||||||
@@ -3075,10 +3123,32 @@
|
|||||||
[
|
[
|
||||||
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
h('div', { key: 'filters', className: 'mc-filter-grid' }, [
|
||||||
selectedOfferType === 'crypto'
|
selectedOfferType === 'crypto'
|
||||||
? inputField('Basispreis fuer 50 kH/s auf 3 Monate', 'number', offerPreviewForm.crypto_base_price_amount, (value) => setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_amount: value }), '0.000001')
|
? fieldWrapper('Basispreis fuer 50 kH/s auf 3 Monate', h('input', {
|
||||||
: inputField('Basispreis fuer 100 kH/s auf 3 Monate in EUR', 'number', offerPreviewForm.fiat_base_price_amount, (value) => setOfferPreviewForm({ ...offerPreviewForm, fiat_base_price_amount: value }), '0.000001'),
|
className: 'mc-input',
|
||||||
|
type: 'number',
|
||||||
|
step: '0.000001',
|
||||||
|
value: offerPreviewForm.crypto_base_price_amount,
|
||||||
|
onChange: (event) => setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_amount: event.target.value }),
|
||||||
|
onBlur: () => rememberOfferBasisInput('crypto', offerPreviewForm.crypto_base_price_amount, offerPreviewForm.crypto_base_price_currency || 'USD'),
|
||||||
|
}))
|
||||||
|
: fieldWrapper('Basispreis fuer 100 kH/s auf 3 Monate in EUR', h('input', {
|
||||||
|
className: 'mc-input',
|
||||||
|
type: 'number',
|
||||||
|
step: '0.000001',
|
||||||
|
value: offerPreviewForm.fiat_base_price_amount,
|
||||||
|
onChange: (event) => setOfferPreviewForm({ ...offerPreviewForm, fiat_base_price_amount: event.target.value }),
|
||||||
|
onBlur: () => rememberOfferBasisInput('fiat', offerPreviewForm.fiat_base_price_amount, 'EUR'),
|
||||||
|
})),
|
||||||
selectedOfferType === 'crypto'
|
selectedOfferType === 'crypto'
|
||||||
? selectField('Basiswaehrung', offerPreviewForm.crypto_base_price_currency || 'USD', Array.from(new Set(['USD', String(currentSettings.crypto_currency || 'DOGE').toUpperCase()])), (value) => setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_currency: value || 'USD' }))
|
? fieldWrapper('Basiswaehrung', h('select', {
|
||||||
|
className: 'mc-select',
|
||||||
|
value: offerPreviewForm.crypto_base_price_currency || 'USD',
|
||||||
|
onChange: async (event) => {
|
||||||
|
const nextCurrency = event.target.value || 'USD';
|
||||||
|
setOfferPreviewForm({ ...offerPreviewForm, crypto_base_price_currency: nextCurrency });
|
||||||
|
await rememberOfferBasisInput('crypto', offerPreviewForm.crypto_base_price_amount, nextCurrency);
|
||||||
|
},
|
||||||
|
}, Array.from(new Set(['USD', String(currentSettings.crypto_currency || 'DOGE').toUpperCase()])).map((value) => h('option', { key: value, value }, value))))
|
||||||
: displayField('Basiswaehrung', 'EUR'),
|
: displayField('Basiswaehrung', 'EUR'),
|
||||||
selectField('Angebotsart', minerOfferFilters.offer_type, [
|
selectField('Angebotsart', minerOfferFilters.offer_type, [
|
||||||
{ value: 'crypto', label: 'Crypto' },
|
{ value: 'crypto', label: 'Crypto' },
|
||||||
@@ -3095,6 +3165,14 @@
|
|||||||
label: `${value} Monate`,
|
label: `${value} Monate`,
|
||||||
}))), (value) => setMinerOfferFilters({ ...minerOfferFilters, runtime_months: value })),
|
}))), (value) => setMinerOfferFilters({ ...minerOfferFilters, runtime_months: value })),
|
||||||
]),
|
]),
|
||||||
|
visibleOfferBasisHistory.length ? h('div', { key: 'basis-history', className: 'mc-mini-grid' }, visibleOfferBasisHistory.map((entry) => h('div', {
|
||||||
|
key: `basis-${entry.id}`,
|
||||||
|
className: 'mc-mini-card',
|
||||||
|
}, [
|
||||||
|
h('div', { key: 'label', className: 'mc-field-label' }, selectedOfferType === 'crypto' ? 'Crypto-Basis' : 'Fiat-Basis'),
|
||||||
|
h('div', { key: 'value' }, `${fmtNumber(entry.base_price_amount, 6)} ${entry.base_price_currency}`),
|
||||||
|
h('div', { key: 'sub', className: 'mc-kicker' }, entry.created_at ? fmtDate(entry.created_at) : 'Zeitpunkt unbekannt'),
|
||||||
|
]))) : null,
|
||||||
h('div', { key: 'offers-table', className: 'mc-table-shell' }, [
|
h('div', { key: 'offers-table', className: 'mc-table-shell' }, [
|
||||||
h('table', { key: 'table', className: 'mc-table' }, [
|
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('thead', { key: 'head' }, h('tr', null, ['Label', 'Hashrate', 'Preis', 'Erwartet/Tag', 'Break-even', 'Empfehlung', 'Aktion'].map((label) => h('th', { key: label }, label)))),
|
||||||
@@ -3102,8 +3180,30 @@
|
|||||||
filteredMinerOffers.length
|
filteredMinerOffers.length
|
||||||
? filteredMinerOffers.map((offer) => h('tr', { key: offer.id }, [
|
? filteredMinerOffers.map((offer) => h('tr', { key: offer.id }, [
|
||||||
h('td', { key: 'label' }, offer.label),
|
h('td', { key: 'label' }, offer.label),
|
||||||
h('td', { key: 'hashrate' }, formatAdaptiveSpeed(offer.offer_hashrate_mh)),
|
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
|
||||||
|
? h('div', { key: 'bonus', className: 'mc-kicker' }, formatSpeed(offer.bonus_speed_value, offer.bonus_speed_unit, 'Bonus'))
|
||||||
|
: h('div', { key: 'bonus', className: 'mc-kicker' }, 'Bonus 0'),
|
||||||
|
h('div', { key: 'total', className: 'mc-kicker' }, `Gesamt ${formatAdaptiveSpeed(offer.offer_hashrate_mh)}`),
|
||||||
|
]),
|
||||||
h('td', { key: 'price' }, [
|
h('td', { key: 'price' }, [
|
||||||
|
offer.payment_type === 'crypto'
|
||||||
|
? [
|
||||||
|
offer.crypto_display_price_amount !== null && offer.crypto_display_price_currency
|
||||||
|
? h('div', { key: 'price-crypto' }, [
|
||||||
|
h('div', { key: 'amount' }, `${fmtNumber(offer.crypto_display_price_amount, 6)} ${offer.crypto_display_price_currency}`),
|
||||||
|
h('div', { key: 'label', className: 'mc-kicker' }, 'Zu zahlen in Krypto'),
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
offer.usd_display_price_amount !== null && offer.usd_display_price_currency
|
||||||
|
? h('div', { key: 'price-usd' }, [
|
||||||
|
h('div', { key: 'amount' }, `${fmtNumber(offer.usd_display_price_amount, 6)} ${offer.usd_display_price_currency}`),
|
||||||
|
h('div', { key: 'label', className: 'mc-kicker' }, 'Zu zahlen in USD'),
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
]
|
||||||
|
: [
|
||||||
h('div', { key: 'price-main' }, [
|
h('div', { key: 'price-main' }, [
|
||||||
h('div', { key: 'amount' }, `${fmtNumber(offer.effective_price_amount, 6)} ${offer.effective_price_currency}`),
|
h('div', { key: 'amount' }, `${fmtNumber(offer.effective_price_amount, 6)} ${offer.effective_price_currency}`),
|
||||||
h('div', { key: 'label', className: 'mc-kicker' }, 'Zu zahlen'),
|
h('div', { key: 'label', className: 'mc-kicker' }, 'Zu zahlen'),
|
||||||
@@ -3114,6 +3214,7 @@
|
|||||||
h('div', { key: 'label', className: 'mc-kicker' }, 'Gegenwert'),
|
h('div', { key: 'label', className: 'mc-kicker' }, 'Gegenwert'),
|
||||||
])
|
])
|
||||||
: null,
|
: null,
|
||||||
|
],
|
||||||
]),
|
]),
|
||||||
h('td', { key: 'day' }, offer.expected_doge_per_day !== null ? `${fmtNumber(offer.expected_doge_per_day, 6)} ${currentCoinCurrency}` : 'n/a'),
|
h('td', { key: 'day' }, offer.expected_doge_per_day !== null ? `${fmtNumber(offer.expected_doge_per_day, 6)} ${currentCoinCurrency}` : 'n/a'),
|
||||||
h('td', { key: 'break' }, offer.break_even_days !== null ? `${fmtNumber(offer.break_even_days, 2)} Tage` : 'n/a'),
|
h('td', { key: 'break' }, offer.break_even_days !== null ? `${fmtNumber(offer.break_even_days, 2)} Tage` : 'n/a'),
|
||||||
|
|||||||
@@ -160,6 +160,18 @@ CREATE TABLE IF NOT EXISTS miningcheck_miner_offers (
|
|||||||
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS miningcheck_offer_basis_history (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
owner_sub VARCHAR(191) NOT NULL DEFAULT 'local',
|
||||||
|
offer_type VARCHAR(16) NOT NULL,
|
||||||
|
base_price_amount DECIMAL(20,8) NOT NULL,
|
||||||
|
base_price_currency VARCHAR(10) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_mining_offer_basis_history_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE,
|
||||||
|
KEY idx_mining_offer_basis_history_project_owner_type_created (project_key, owner_sub, offer_type, created_at)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
||||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
project_key VARCHAR(64) NOT NULL,
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
|||||||
@@ -173,6 +173,19 @@ CREATE TABLE IF NOT EXISTS miningcheck_miner_offers (
|
|||||||
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS miningcheck_offer_basis_history (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
owner_sub VARCHAR(191) NOT NULL DEFAULT 'local',
|
||||||
|
offer_type VARCHAR(16) NOT NULL,
|
||||||
|
base_price_amount NUMERIC(20,8) NOT NULL,
|
||||||
|
base_price_currency VARCHAR(10) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_mining_offer_basis_history_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_mining_offer_basis_history_project_owner_type_created
|
||||||
|
ON miningcheck_offer_basis_history(project_key, owner_sub, offer_type, created_at);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
project_key VARCHAR(64) NOT NULL,
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
|||||||
@@ -160,6 +160,18 @@ CREATE TABLE IF NOT EXISTS miningcheck_miner_offers (
|
|||||||
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS miningcheck_offer_basis_history (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
owner_sub VARCHAR(191) NOT NULL DEFAULT 'local',
|
||||||
|
offer_type VARCHAR(16) NOT NULL,
|
||||||
|
base_price_amount DECIMAL(20,8) NOT NULL,
|
||||||
|
base_price_currency VARCHAR(10) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_mining_offer_basis_history_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE,
|
||||||
|
KEY idx_mining_offer_basis_history_project_owner_type_created (project_key, owner_sub, offer_type, created_at)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
CREATE TABLE IF NOT EXISTS miningcheck_targets (
|
||||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
project_key VARCHAR(64) NOT NULL,
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
|||||||
@@ -153,6 +153,15 @@ final class Router
|
|||||||
$this->respond(['data' => $this->fxHistory()]);
|
$this->respond(['data' => $this->fxHistory()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($resource === 'offer-basis-history' && $method === 'GET') {
|
||||||
|
$this->respond(['data' => $this->offerBasisHistory($projectKey)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($resource === 'offer-basis-history' && $method === 'POST') {
|
||||||
|
$this->repository()->ensureProject($projectKey);
|
||||||
|
$this->respond(['data' => $this->saveOfferBasisHistory($projectKey, Http::input())], 201);
|
||||||
|
}
|
||||||
|
|
||||||
if ($resource === 'legacy-fx-migrate' && $method === 'POST') {
|
if ($resource === 'legacy-fx-migrate' && $method === 'POST') {
|
||||||
$this->respond(['data' => $this->migrateLegacyFxRates($projectKey)], 201);
|
$this->respond(['data' => $this->migrateLegacyFxRates($projectKey)], 201);
|
||||||
}
|
}
|
||||||
@@ -818,6 +827,11 @@ final class Router
|
|||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function offerBasisHistory(string $projectKey): array
|
||||||
|
{
|
||||||
|
return $this->repository()->listOfferBasisHistory($projectKey, 50);
|
||||||
|
}
|
||||||
|
|
||||||
private function migrateLegacyFxRates(string $projectKey): array
|
private function migrateLegacyFxRates(string $projectKey): array
|
||||||
{
|
{
|
||||||
$fxRepository = $this->sharedFxRatesRepository();
|
$fxRepository = $this->sharedFxRatesRepository();
|
||||||
@@ -1694,6 +1708,21 @@ final class Router
|
|||||||
}
|
}
|
||||||
|
|
||||||
$measurements = $this->measurements($projectKey, $settings);
|
$measurements = $this->measurements($projectKey, $settings);
|
||||||
|
$freshFetchId = null;
|
||||||
|
if ($settings['crypto_base_price_amount'] !== null) {
|
||||||
|
try {
|
||||||
|
$fresh = $this->fx()->refreshLatestRates(null, 'USD');
|
||||||
|
$freshFetchId = is_numeric($fresh['fetch_id'] ?? null) ? (int) $fresh['fetch_id'] : null;
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$freshFetchId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($freshFetchId !== null && $freshFetchId > 0 && $measurements !== []) {
|
||||||
|
$latestIndex = array_key_last($measurements);
|
||||||
|
if ($latestIndex !== null && is_array($measurements[$latestIndex])) {
|
||||||
|
$measurements[$latestIndex]['fx_fetch_id'] = $freshFetchId;
|
||||||
|
}
|
||||||
|
}
|
||||||
$summary = $this->analytics()->buildSummary($measurements, $settings, [], [
|
$summary = $this->analytics()->buildSummary($measurements, $settings, [], [
|
||||||
'include_offer_scenarios' => true,
|
'include_offer_scenarios' => true,
|
||||||
'include_long_term_projection' => true,
|
'include_long_term_projection' => true,
|
||||||
@@ -1701,9 +1730,38 @@ final class Router
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'miner_offers' => is_array($summary['miner_offers'] ?? null) ? $summary['miner_offers'] : [],
|
'miner_offers' => is_array($summary['miner_offers'] ?? null) ? $summary['miner_offers'] : [],
|
||||||
|
'fx_fetch_id' => $freshFetchId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function saveOfferBasisHistory(string $projectKey, array $input): array
|
||||||
|
{
|
||||||
|
$offerType = $this->enumValue($input['offer_type'] ?? null, ['crypto', 'fiat'], 'offer_type');
|
||||||
|
$basePriceAmount = $this->requiredDecimal($input['base_price_amount'] ?? null, 'base_price_amount');
|
||||||
|
if ($basePriceAmount <= 0) {
|
||||||
|
throw new ApiException('base_price_amount muss groesser als 0 sein.', 422, ['field' => 'base_price_amount']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePriceCurrency = $offerType === 'crypto'
|
||||||
|
? $this->requiredCurrency($input['base_price_currency'] ?? 'USD', 'base_price_currency')
|
||||||
|
: 'EUR';
|
||||||
|
|
||||||
|
if ($offerType === 'crypto') {
|
||||||
|
$cryptoCurrency = $this->requiredCurrency($this->settings($projectKey)['crypto_currency'] ?? 'DOGE', 'crypto_currency');
|
||||||
|
if (!in_array($basePriceCurrency, ['USD', $cryptoCurrency], true)) {
|
||||||
|
throw new ApiException('Bei Crypto-Basispreisen sind nur USD oder die Projekt-Kryptowaehrung erlaubt.', 422, [
|
||||||
|
'field' => 'base_price_currency',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->repository()->saveOfferBasisEntry($projectKey, [
|
||||||
|
'offer_type' => $offerType,
|
||||||
|
'base_price_amount' => $basePriceAmount,
|
||||||
|
'base_price_currency' => $basePriceCurrency,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private function purchasedMiners(string $projectKey): array
|
private function purchasedMiners(string $projectKey): array
|
||||||
{
|
{
|
||||||
return $this->repository()->listPurchasedMiners($projectKey);
|
return $this->repository()->listPurchasedMiners($projectKey);
|
||||||
|
|||||||
@@ -1188,6 +1188,23 @@ final class AnalyticsService
|
|||||||
}
|
}
|
||||||
$referencePriceAmount = $basePriceAmount;
|
$referencePriceAmount = $basePriceAmount;
|
||||||
$referencePriceCurrency = $basePriceCurrency !== '' ? $basePriceCurrency : null;
|
$referencePriceCurrency = $basePriceCurrency !== '' ? $basePriceCurrency : null;
|
||||||
|
$cryptoDisplayAmount = null;
|
||||||
|
$cryptoDisplayCurrency = null;
|
||||||
|
$usdDisplayAmount = null;
|
||||||
|
$usdDisplayCurrency = null;
|
||||||
|
|
||||||
|
if ($paymentType === 'crypto') {
|
||||||
|
$cryptoDisplayCurrency = strtoupper(trim((string) ($settings['crypto_currency'] ?? 'DOGE')));
|
||||||
|
$cryptoDisplayAmount = $this->convertAmount($basePriceAmount, $basePriceCurrency, $cryptoDisplayCurrency, $latest);
|
||||||
|
if ($cryptoDisplayAmount === null && strtoupper($basePriceCurrency) === $cryptoDisplayCurrency) {
|
||||||
|
$cryptoDisplayAmount = $basePriceAmount;
|
||||||
|
}
|
||||||
|
$usdDisplayCurrency = 'USD';
|
||||||
|
$usdDisplayAmount = $this->convertAmount($basePriceAmount, $basePriceCurrency, 'USD', $latest);
|
||||||
|
if ($usdDisplayAmount === null && strtoupper($basePriceCurrency) === 'USD') {
|
||||||
|
$usdDisplayAmount = $basePriceAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$expectedDogePerDay = null;
|
$expectedDogePerDay = null;
|
||||||
if ($currentHashrateMh > 0 && $offerHashrateMh > 0 && is_numeric($latest['doge_per_day_interval'] ?? null)) {
|
if ($currentHashrateMh > 0 && $offerHashrateMh > 0 && is_numeric($latest['doge_per_day_interval'] ?? null)) {
|
||||||
@@ -1202,16 +1219,11 @@ final class AnalyticsService
|
|||||||
? $effectivePriceAmount / $expectedDailyRevenue
|
? $effectivePriceAmount / $expectedDailyRevenue
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
$recommendation = 'keine Basis';
|
$recommendation = $this->offerRecommendation(
|
||||||
if ($breakEvenDays !== null) {
|
$breakEvenDays,
|
||||||
if ($breakEvenDays <= 180) {
|
(int) ($offer['runtime_months'] ?? 0),
|
||||||
$recommendation = 'lohnt eher';
|
!empty($offer['auto_renew'])
|
||||||
} elseif ($breakEvenDays <= 365) {
|
);
|
||||||
$recommendation = 'abwaegen';
|
|
||||||
} else {
|
|
||||||
$recommendation = 'eher warten';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_merge($offer, [
|
return array_merge($offer, [
|
||||||
'payment_type' => $paymentType,
|
'payment_type' => $paymentType,
|
||||||
@@ -1222,6 +1234,10 @@ final class AnalyticsService
|
|||||||
'effective_price_currency' => $effectivePriceCurrency,
|
'effective_price_currency' => $effectivePriceCurrency,
|
||||||
'reference_price_amount' => $this->roundOrNull($referencePriceAmount, 8),
|
'reference_price_amount' => $this->roundOrNull($referencePriceAmount, 8),
|
||||||
'reference_price_currency' => $referencePriceCurrency !== '' ? $referencePriceCurrency : null,
|
'reference_price_currency' => $referencePriceCurrency !== '' ? $referencePriceCurrency : null,
|
||||||
|
'crypto_display_price_amount' => $this->roundOrNull($cryptoDisplayAmount, 8),
|
||||||
|
'crypto_display_price_currency' => $cryptoDisplayCurrency,
|
||||||
|
'usd_display_price_amount' => $this->roundOrNull($usdDisplayAmount, 8),
|
||||||
|
'usd_display_price_currency' => $usdDisplayCurrency,
|
||||||
'expected_doge_per_day' => $this->roundOrNull($expectedDogePerDay, 6),
|
'expected_doge_per_day' => $this->roundOrNull($expectedDogePerDay, 6),
|
||||||
'expected_daily_revenue' => $this->roundOrNull($expectedDailyRevenue, 8),
|
'expected_daily_revenue' => $this->roundOrNull($expectedDailyRevenue, 8),
|
||||||
'break_even_days' => $this->roundOrNull($breakEvenDays, 2),
|
'break_even_days' => $this->roundOrNull($breakEvenDays, 2),
|
||||||
@@ -1229,6 +1245,34 @@ final class AnalyticsService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function offerRecommendation(?float $breakEvenDays, int $runtimeMonths, bool $autoRenew): string
|
||||||
|
{
|
||||||
|
if ($breakEvenDays === null) {
|
||||||
|
return 'keine Basis';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$autoRenew && $runtimeMonths > 0) {
|
||||||
|
$runtimeDays = $runtimeMonths * 30.0;
|
||||||
|
if ($breakEvenDays <= ($runtimeDays * 0.85)) {
|
||||||
|
return 'lohnt eher';
|
||||||
|
}
|
||||||
|
if ($breakEvenDays <= $runtimeDays) {
|
||||||
|
return 'abwaegen';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'eher warten';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($breakEvenDays <= 180) {
|
||||||
|
return 'lohnt eher';
|
||||||
|
}
|
||||||
|
if ($breakEvenDays <= 365) {
|
||||||
|
return 'abwaegen';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'eher warten';
|
||||||
|
}
|
||||||
|
|
||||||
private function enrichOfferScenario(array $offer, array $latest, float $currentHashrateMh, array $costPlans, array $purchasedMiners): array
|
private function enrichOfferScenario(array $offer, array $latest, float $currentHashrateMh, array $costPlans, array $purchasedMiners): array
|
||||||
{
|
{
|
||||||
$latestCurrency = (string) ($latest['effective_price_currency'] ?? $latest['price_currency'] ?? '');
|
$latestCurrency = (string) ($latest['effective_price_currency'] ?? $latest['price_currency'] ?? '');
|
||||||
|
|||||||
@@ -111,6 +111,92 @@ final class MiningRepository
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listOfferBasisHistory(string $projectKey, int $limit = 50): array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('offer_basis_history') . '
|
||||||
|
WHERE project_key = :project_key AND owner_sub = :owner_sub
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
LIMIT :limit'
|
||||||
|
);
|
||||||
|
$stmt->bindValue(':project_key', $projectKey, PDO::PARAM_STR);
|
||||||
|
$stmt->bindValue(':owner_sub', $this->ownerSub, PDO::PARAM_STR);
|
||||||
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
return $this->normalizeRows($stmt->fetchAll() ?: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function latestOfferBasisEntry(string $projectKey, string $offerType): ?array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('offer_basis_history') . '
|
||||||
|
WHERE project_key = :project_key AND owner_sub = :owner_sub AND offer_type = :offer_type
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'offer_type' => strtolower(trim($offerType)),
|
||||||
|
]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
return is_array($row) ? $this->normalizeRow($row) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveOfferBasisEntry(string $projectKey, array $payload): array
|
||||||
|
{
|
||||||
|
$normalizedType = strtolower(trim((string) ($payload['offer_type'] ?? '')));
|
||||||
|
$latest = $this->latestOfferBasisEntry($projectKey, $normalizedType);
|
||||||
|
if (
|
||||||
|
is_array($latest)
|
||||||
|
&& (string) ($latest['base_price_currency'] ?? '') === (string) ($payload['base_price_currency'] ?? '')
|
||||||
|
&& is_numeric($latest['base_price_amount'] ?? null)
|
||||||
|
&& is_numeric($payload['base_price_amount'] ?? null)
|
||||||
|
&& abs((float) $latest['base_price_amount'] - (float) $payload['base_price_amount']) < 0.00000001
|
||||||
|
) {
|
||||||
|
return $latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->driver === 'pgsql') {
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'INSERT INTO ' . $this->table('offer_basis_history') . ' (
|
||||||
|
project_key, owner_sub, offer_type, base_price_amount, base_price_currency
|
||||||
|
) VALUES (
|
||||||
|
:project_key, :owner_sub, :offer_type, :base_price_amount, :base_price_currency
|
||||||
|
)
|
||||||
|
RETURNING *'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'offer_type' => $normalizedType,
|
||||||
|
'base_price_amount' => $payload['base_price_amount'],
|
||||||
|
'base_price_currency' => $payload['base_price_currency'],
|
||||||
|
]);
|
||||||
|
return $this->normalizeRow($stmt->fetch() ?: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'INSERT INTO ' . $this->table('offer_basis_history') . ' (
|
||||||
|
project_key, owner_sub, offer_type, base_price_amount, base_price_currency
|
||||||
|
) VALUES (
|
||||||
|
:project_key, :owner_sub, :offer_type, :base_price_amount, :base_price_currency
|
||||||
|
)'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'offer_type' => $normalizedType,
|
||||||
|
'base_price_amount' => $payload['base_price_amount'],
|
||||||
|
'base_price_currency' => $payload['base_price_currency'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$id = (int) $this->pdo->lastInsertId();
|
||||||
|
$fetch = $this->pdo->prepare('SELECT * FROM ' . $this->table('offer_basis_history') . ' WHERE id = :id LIMIT 1');
|
||||||
|
$fetch->execute(['id' => $id]);
|
||||||
|
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||||
|
}
|
||||||
|
|
||||||
public function tableExists(string $logicalName): bool
|
public function tableExists(string $logicalName): bool
|
||||||
{
|
{
|
||||||
$tableName = $this->table($logicalName);
|
$tableName = $this->table($logicalName);
|
||||||
@@ -1384,6 +1470,7 @@ final class MiningRepository
|
|||||||
'wallet_snapshots' => $this->prefix . 'wallet_snapshots',
|
'wallet_snapshots' => $this->prefix . 'wallet_snapshots',
|
||||||
'wallet_withdrawals' => $this->prefix . 'wallet_withdrawals',
|
'wallet_withdrawals' => $this->prefix . 'wallet_withdrawals',
|
||||||
'miner_offers' => $this->prefix . 'miner_offers',
|
'miner_offers' => $this->prefix . 'miner_offers',
|
||||||
|
'offer_basis_history' => $this->prefix . 'offer_basis_history',
|
||||||
'purchased_miners' => $this->prefix . 'purchased_miners',
|
'purchased_miners' => $this->prefix . 'purchased_miners',
|
||||||
default => throw new \RuntimeException('Unknown mining table: ' . $logicalName),
|
default => throw new \RuntimeException('Unknown mining table: ' . $logicalName),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ final class SchemaManager
|
|||||||
$this->upgradeMinerOffersTable();
|
$this->upgradeMinerOffersTable();
|
||||||
$applied[] = 'miner_offers_table';
|
$applied[] = 'miner_offers_table';
|
||||||
}
|
}
|
||||||
|
if (!$this->tableExists($this->prefix . 'offer_basis_history')) {
|
||||||
|
$this->ensureOfferBasisHistoryTable();
|
||||||
|
$applied[] = 'offer_basis_history_table';
|
||||||
|
}
|
||||||
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
||||||
$this->upgradePurchasedMinersTable();
|
$this->upgradePurchasedMinersTable();
|
||||||
$applied[] = 'purchased_miners_table';
|
$applied[] = 'purchased_miners_table';
|
||||||
@@ -337,6 +341,10 @@ final class SchemaManager
|
|||||||
$this->upgradeMinerOffersTable();
|
$this->upgradeMinerOffersTable();
|
||||||
$applied[] = 'miner_offers_table';
|
$applied[] = 'miner_offers_table';
|
||||||
}
|
}
|
||||||
|
if (!$this->tableExists($this->prefix . 'offer_basis_history')) {
|
||||||
|
$this->ensureOfferBasisHistoryTable();
|
||||||
|
$applied[] = 'offer_basis_history_table';
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
||||||
$this->upgradePurchasedMinersTable();
|
$this->upgradePurchasedMinersTable();
|
||||||
@@ -693,6 +701,9 @@ final class SchemaManager
|
|||||||
if (!$this->tableExists($this->prefix . 'miner_offers')) {
|
if (!$this->tableExists($this->prefix . 'miner_offers')) {
|
||||||
$upgrades[] = 'miner_offers_table';
|
$upgrades[] = 'miner_offers_table';
|
||||||
}
|
}
|
||||||
|
if (!$this->tableExists($this->prefix . 'offer_basis_history')) {
|
||||||
|
$upgrades[] = 'offer_basis_history_table';
|
||||||
|
}
|
||||||
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
|
||||||
$upgrades[] = 'purchased_miners_table';
|
$upgrades[] = 'purchased_miners_table';
|
||||||
}
|
}
|
||||||
@@ -1179,6 +1190,45 @@ final class SchemaManager
|
|||||||
$this->executeUpgradeStatements($statements, 'Schema-Upgrade fuer Miner-Angebote fehlgeschlagen.');
|
$this->executeUpgradeStatements($statements, 'Schema-Upgrade fuer Miner-Angebote fehlgeschlagen.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ensureOfferBasisHistoryTable(): void
|
||||||
|
{
|
||||||
|
if ($this->tableExists($this->prefix . 'offer_basis_history')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->prefix . 'offer_basis_history';
|
||||||
|
$projectTable = $this->prefix . 'projects';
|
||||||
|
$statements = $this->driver === 'pgsql'
|
||||||
|
? [
|
||||||
|
'CREATE TABLE IF NOT EXISTS ' . $table . ' (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
owner_sub VARCHAR(191) NOT NULL DEFAULT \'local\',
|
||||||
|
offer_type VARCHAR(16) NOT NULL,
|
||||||
|
base_price_amount NUMERIC(20,8) NOT NULL,
|
||||||
|
base_price_currency VARCHAR(10) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_mining_offer_basis_history_project FOREIGN KEY (project_key) REFERENCES ' . $projectTable . '(project_key) ON DELETE CASCADE
|
||||||
|
)',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_mining_offer_basis_history_project_owner_type_created ON ' . $table . ' (project_key, owner_sub, offer_type, created_at)',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'CREATE TABLE IF NOT EXISTS `' . $table . '` (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
project_key VARCHAR(64) NOT NULL,
|
||||||
|
owner_sub VARCHAR(191) NOT NULL DEFAULT \'local\',
|
||||||
|
offer_type VARCHAR(16) NOT NULL,
|
||||||
|
base_price_amount DECIMAL(20,8) NOT NULL,
|
||||||
|
base_price_currency VARCHAR(10) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_mining_offer_basis_history_project FOREIGN KEY (project_key) REFERENCES `' . $projectTable . '`(project_key) ON DELETE CASCADE,
|
||||||
|
KEY idx_mining_offer_basis_history_project_owner_type_created (project_key, owner_sub, offer_type, created_at)
|
||||||
|
)',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->executeUpgradeStatements($statements, 'Schema-Upgrade fuer Angebots-Basis-Historie fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
|
||||||
private function upgradePurchasedMinersTable(): void
|
private function upgradePurchasedMinersTable(): void
|
||||||
{
|
{
|
||||||
$table = $this->prefix . 'purchased_miners';
|
$table = $this->prefix . 'purchased_miners';
|
||||||
@@ -1528,6 +1578,7 @@ final class SchemaManager
|
|||||||
$this->prefix . 'wallet_snapshots',
|
$this->prefix . 'wallet_snapshots',
|
||||||
$this->prefix . 'wallet_withdrawals',
|
$this->prefix . 'wallet_withdrawals',
|
||||||
$this->prefix . 'miner_offers',
|
$this->prefix . 'miner_offers',
|
||||||
|
$this->prefix . 'offer_basis_history',
|
||||||
$this->prefix . 'targets',
|
$this->prefix . 'targets',
|
||||||
$this->prefix . 'dashboard_definitions',
|
$this->prefix . 'dashboard_definitions',
|
||||||
$this->prefix . 'purchased_miners',
|
$this->prefix . 'purchased_miners',
|
||||||
|
|||||||
Reference in New Issue
Block a user