(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();
}
})();