(function () { 'use strict'; var state = { data: null, mode: 'seller', selectedSellerID: '', busy: false, }; function $(id) { return document.getElementById(id); } function t(key, fallback) { return window.HyAppI18n && window.HyAppI18n.t ? window.HyAppI18n.t(key, fallback) : fallback || key; } function toast(message) { if (window.HyAppToast) window.HyAppToast.show(message); } function number(value) { var normalized = value || 0; if ( typeof BigInt === 'function' && typeof normalized === 'string' && /^-?\d+$/.test(normalized) ) { return new Intl.NumberFormat().format(BigInt(normalized)); } return Number(normalized).toLocaleString(undefined, { maximumFractionDigits: 0, }); } function amount() { var value = Number($('pointAmount').value || 0); return Number.isSafeInteger(value) && value > 0 ? value : 0; } function selectedSeller() { if (!state.data) return null; return state.data.sellers.find(function (seller) { return seller.userId === state.selectedSellerID; }); } function setBusy(value) { state.busy = value; document.body.classList.toggle('is-busy', value); $('sellerSubmit').disabled = value || !selectedSeller(); $('usdtSubmit').disabled = value; $('saveAddressButton').disabled = value; } function renderOverview() { if (!state.data) return; $('pointBalance').textContent = number(state.data.balance.available); $('balanceUSD').textContent = '≈ ' + state.data.balance.displayUSD + ' USDT'; $('addressInput').value = state.data.address || ''; $('addressStatus').classList.toggle( 'has-address', Boolean(state.data.address) ); $('addressStatus').textContent = state.data.address ? t('fami_wallet.saved_address', 'Saved address:') + ' ' + state.data.address : t('fami_wallet.no_address', 'No USDT address saved.'); $('policyCaption').textContent = t('fami_wallet.policy_caption', 'Rate and fee are set by Admin.') + ' · 1 USDT = ' + number(state.data.pointsPerUSD) + ' POINT · ' + (state.data.feeBPS / 100).toFixed(2) + '%'; if (!$('pointAmount').value && state.data.minimumPoints > 0) { $('pointAmount').value = String(state.data.minimumPoints); } renderConversion(); } function renderSellers() { var sellers = state.data ? state.data.sellers : []; if ( sellers.length && !sellers.some(function (seller) { return seller.userId === state.selectedSellerID; }) ) { state.selectedSellerID = sellers[0].userId; } $('sellerList').innerHTML = sellers .map(function (seller) { var selected = seller.userId === state.selectedSellerID; var avatar = seller.avatar ? '' : escapeHTML( (seller.nickname || 'C').charAt(0).toUpperCase() ); return ( '' ); }) .join(''); $('sellerEmpty').hidden = sellers.length > 0; setBusy(state.busy); } function renderHistory() { var items = state.data ? state.data.history || [] : []; $('historyList').innerHTML = items .map(function (item) { var seller = item.counterpartyUserId ? ' · UID ' + escapeHTML(item.counterpartyUserId) : ''; return ( '
' + escapeHTML(historyLabel(item.bizType)) + seller + '' + (item.availableDelta > 0 ? '+' : '') + number(item.availableDelta) + '' + dateTime(item.createdAtMS) + '
' ); }) .join(''); $('historyEmpty').textContent = t( 'fami_wallet.no_history', 'No withdrawal records' ); $('historyEmpty').hidden = items.length > 0; if (state.data && state.data.historyUnavailable) { $('historyEmpty').hidden = false; $('historyEmpty').textContent = t( 'fami_wallet.history_unavailable', 'History is temporarily unavailable.' ); } } function historyLabel(bizType) { if (bizType === 'point_transfer_to_coin_seller') { return t('fami_wallet.seller_withdrawal', 'Coin seller withdrawal'); } if (String(bizType).indexOf('withdrawal') >= 0) { return t('fami_wallet.usdt_withdrawal', 'USDT withdrawal'); } return t('fami_wallet.point_transaction', 'POINT transaction'); } function renderConversion() { var points = amount(); if (!state.data || !points) { $('estimatedReceive').textContent = '—'; return; } if (state.mode === 'seller') { var seller = selectedSeller(); var receive = seller ? ratioAmount(points, seller) : '0'; $('estimatedReceive').textContent = number(receive) + ' ' + t('fami_wallet.seller_coin', 'seller coins'); return; } var fee = Math.floor((points * state.data.feeBPS) / 10000); var net = points - fee; $('estimatedReceive').textContent = (net / state.data.pointsPerUSD).toFixed(2) + ' USDT'; } function ratioAmount(points, seller) { // 余额可达到万亿级,Number 中间乘法会超过安全整数;BigInt 只用于前端预计值,最终到账仍由 wallet-service 计算。 if (typeof BigInt === 'function') { return ( (BigInt(points) * BigInt(seller.sellerCoinAmount)) / BigInt(seller.pointAmount) ).toString(); } return String( Math.floor((points * seller.sellerCoinAmount) / seller.pointAmount) ); } function selectMode(mode) { state.mode = mode === 'usdt' ? 'usdt' : 'seller'; $('sellerPanel').hidden = state.mode !== 'seller'; $('usdtPanel').hidden = state.mode !== 'usdt'; document.querySelectorAll('[data-mode]').forEach(function (button) { button.setAttribute( 'aria-selected', button.getAttribute('data-mode') === state.mode ? 'true' : 'false' ); }); renderConversion(); } function load() { setBusy(true); return window.HyGuild.wallet .load() .then(function (data) { state.data = data; renderOverview(); renderSellers(); renderHistory(); }) .catch(function () { toast(t('fami_wallet.load_failed', 'Unable to load wallet.')); }) .finally(function () { setBusy(false); }); } function submitSeller() { var seller = selectedSeller(); var points = amount(); if (!seller || !validateAmount(points, state.data.minimumPoints)) return; setBusy(true); window.HyGuild.wallet .transferToCoinSeller(seller.userId, points) .then(function () { toast(t('fami_wallet.submitted', 'Withdrawal submitted.')); return load(); }) .catch(function () { toast( t( 'fami_wallet.submit_failed', 'Unable to submit withdrawal.' ) ); }) .finally(function () { setBusy(false); }); } function submitUSDT() { var points = amount(); if (!validateAmount(points, state.data.minimumPoints)) return; var address = String($('addressInput').value || '').trim(); if (!validTRC20(address)) { toast( t('fami_wallet.invalid_address', 'Enter a valid TRC20 address.') ); return; } setBusy(true); window.HyGuild.wallet .withdrawUSDT(points, address) .then(function () { toast(t('fami_wallet.submitted', 'Withdrawal submitted.')); return load(); }) .catch(function () { toast( t( 'fami_wallet.submit_failed', 'Unable to submit withdrawal.' ) ); }) .finally(function () { setBusy(false); }); } function saveAddress() { var address = String($('addressInput').value || '').trim(); if (!validTRC20(address)) { toast( t('fami_wallet.invalid_address', 'Enter a valid TRC20 address.') ); return; } setBusy(true); window.HyGuild.wallet .saveAddress(address) .then(function () { state.data.address = address; renderOverview(); toast(t('fami_wallet.address_saved', 'Address saved.')); }) .catch(function () { toast( t( 'fami_wallet.address_save_failed', 'Unable to save address.' ) ); }) .finally(function () { setBusy(false); }); } function validateAmount(points, minimum) { if (!points || !state.data || points > state.data.balance.available) { toast( t( 'fami_wallet.invalid_amount', 'Enter an available POINT amount.' ) ); return false; } if (points < minimum) { toast( t('fami_wallet.minimum_amount', 'Minimum POINT amount:') + ' ' + number(minimum) ); return false; } return true; } function validTRC20(value) { return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(value); } function escapeHTML(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function dateTime(value) { if (!Number(value)) return '—'; return new Date(Number(value)).toLocaleString(); } function bind() { $('backButton').addEventListener('click', function () { if (window.history.length > 1) window.history.back(); else if (window.HyAppBridge) window.HyAppBridge.back(); }); document .querySelector('.wallet-tabs') .addEventListener('click', function (event) { var button = event.target.closest('[data-mode]'); if (button) selectMode(button.getAttribute('data-mode')); }); $('sellerList').addEventListener('click', function (event) { var button = event.target.closest('[data-seller-id]'); if (!button || state.busy) return; state.selectedSellerID = button.getAttribute('data-seller-id'); renderSellers(); renderConversion(); }); $('pointAmount').addEventListener('input', renderConversion); $('allButton').addEventListener('click', function () { if (!state.data) return; $('pointAmount').value = String(state.data.balance.available); renderConversion(); }); $('sellerSubmit').addEventListener('click', submitSeller); $('usdtSubmit').addEventListener('click', submitUSDT); $('saveAddressButton').addEventListener('click', saveAddress); window.addEventListener('hyapp:i18n-ready', function () { renderOverview(); renderHistory(); }); } function init() { bind(); selectMode('seller'); load().finally(function () { if (window.HyAppBridge) { window.HyAppBridge.ready({ page: 'fami-wallet', app_code: 'fami', }); } }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();