diff --git a/VERSION.properties b/VERSION.properties
index 7ec9670..b7eec9f 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.2.287
-server.version=1.2.267
+client.version=1.2.288
+server.version=1.2.268
diff --git a/shine-UI/js/pages/wallet-view.js b/shine-UI/js/pages/wallet-view.js
index 95dda6b..4e71b68 100644
--- a/shine-UI/js/pages/wallet-view.js
+++ b/shine-UI/js/pages/wallet-view.js
@@ -1,8 +1,6 @@
import { renderHeader } from '../components/header.js';
import { authService, state } from '../state.js';
import {
- createRandomSolanaWallet,
- createSolanaWalletFromPrivateBase58,
formatSol,
getBalanceSol,
getTopupSiteUrl,
@@ -27,7 +25,6 @@ import {
} from '../services/shine-blockchain-wallet-service.js?v=202605300007';
export const pageMeta = { id: 'wallet-view', title: 'Кошелёк' };
-const SOLANA_PRIVATE_BASE58_MAX_LEN = 44;
function nowRu() {
return new Date().toLocaleString('ru-RU');
@@ -102,6 +99,32 @@ function concatBytes(...parts) {
return out;
}
+const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
+function encodeBase58(bytes) {
+ const source = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []);
+ if (source.length === 0) return '';
+
+ const digits = [0];
+ for (let i = 0; i < source.length; i += 1) {
+ let carry = source[i];
+ for (let j = 0; j < digits.length; j += 1) {
+ const value = (digits[j] << 8) + carry;
+ digits[j] = value % 58;
+ carry = Math.floor(value / 58);
+ }
+ while (carry > 0) {
+ digits.push(carry % 58);
+ carry = Math.floor(carry / 58);
+ }
+ }
+
+ for (let i = 0; i < source.length && source[i] === 0; i += 1) {
+ digits.push(0);
+ }
+
+ return digits.reverse().map((digit) => BASE58_ALPHABET[digit]).join('');
+}
+
function u64ToBytes(value) {
const out = new Uint8Array(8);
let current = BigInt(value || 0);
@@ -271,7 +294,7 @@ async function deriveSupportRandomWallet(extraText) {
const keypair = solana.Keypair.fromSeed(seed);
return {
address: keypair.publicKey.toBase58(),
- privateKey32Base58: solana.bs58.encode(seed),
+ privateKey32Base58: encodeBase58(seed),
keypair,
generatedAt: new Date().toLocaleString('ru-RU'),
};
@@ -406,6 +429,14 @@ export function render({ navigate }) {
arweaveWalletCtx = null;
}
+ function styleSupportInputField(field) {
+ if (!field) return;
+ field.style.color = '#111111';
+ field.style.webkitTextFillColor = '#111111';
+ field.style.caretColor = '#111111';
+ field.style.backgroundColor = '#ffffff';
+ }
+
function renderSupportHub() {
activeModeToken += 1;
clearArweaveSecretsInMemory();
@@ -433,7 +464,7 @@ export function render({ navigate }) {
`;
actions.querySelector('#support-buy')?.addEventListener('click', () => {
- void renderSupportBuy();
+ void renderSupportBuyIntro();
});
actions.querySelector('#support-queue')?.addEventListener('click', () => {
void renderSupportQueue();
@@ -446,7 +477,7 @@ export function render({ navigate }) {
setStatus('Выберите действие в разделе поддержки.');
}
- async function renderSupportHelp(backTarget = renderSupportBuy) {
+ async function renderSupportHelp(backTarget = renderSupportBuyForm) {
const modeToken = ++activeModeToken;
clearArweaveSecretsInMemory();
content.innerHTML = '';
@@ -492,6 +523,7 @@ export function render({ navigate }) {
saltInput.rows = 3;
saltInput.placeholder = 'Можно оставить пустым или добавить любой текст как дополнительную примесь';
saltInput.spellcheck = false;
+ styleSupportInputField(saltInput);
const timeLabel = document.createElement('p');
timeLabel.className = 'meta-muted';
@@ -507,6 +539,7 @@ export function render({ navigate }) {
generatedPublicInput.type = 'text';
generatedPublicInput.readOnly = true;
generatedPublicInput.placeholder = 'Появится после генерации';
+ styleSupportInputField(generatedPublicInput);
const generatedSecretLabel = document.createElement('label');
generatedSecretLabel.className = 'meta-muted';
@@ -519,6 +552,7 @@ export function render({ navigate }) {
generatedSecretInput.readOnly = true;
generatedSecretInput.placeholder = 'Появится после генерации';
generatedSecretInput.spellcheck = false;
+ styleSupportInputField(generatedSecretInput);
const generatedAddressNote = document.createElement('p');
generatedAddressNote.className = 'meta-muted';
@@ -661,6 +695,7 @@ export function render({ navigate }) {
queryInput.placeholder = 'Например: 12, 2-5 или 3 8';
queryInput.autocomplete = 'off';
queryInput.spellcheck = false;
+ styleSupportInputField(queryInput);
const actions = document.createElement('div');
actions.className = 'row';
@@ -737,19 +772,89 @@ export function render({ navigate }) {
setStatus('Просмотр билета готов. Введите номер в нужном формате.');
}
- async function renderSupportBuy() {
+ async function renderSupportBuyIntro() {
const modeToken = ++activeModeToken;
clearArweaveSecretsInMemory();
content.innerHTML = '';
const backBtn = createModeBackButton(renderSupportHub);
+ const introCard = document.createElement('div');
+ introCard.className = 'card stack';
+ introCard.innerHTML = `
+
Купить билет
+
+ Сначала показываем условия и текущий лимит. После этого можно перейти к покупке и ввести сумму в долларах.
+ Оплата идет в SOL, а расчет строится по курсу USD/USDT на момент транзакции.
+
- Вы покупаете только билет очереди 1. Сумма задается в USD, а оплата уходит в SOL по курсу USDT/SOL на момент транзакции.
- Покупка подписывается вашим client key. Интерфейс проверяет допуск по курсу до 3 процентов и не дает выходить за текущий лимит по коэффициенту.
+ Здесь вводится сумма покупки и адрес получателя. Если адрес не указан, можно купить на тот же кошелёк, с которого идет оплата.
+ Покупка подписывается вашим client key и отклоняется, если курс уходит дальше допустимого порога.