From 684f3237cf9eb578a969d511f20f73724be7e2281ffdd6a6f76820a301648325 Mon Sep 17 00:00:00 2001
From: AidarKC
Date: Wed, 24 Jun 2026 10:28:54 +0400
Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?=
=?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?=
=?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8?=
=?UTF-8?q?=D1=81=D1=8C=20=D0=B2=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5?=
=?UTF-8?q?=D1=80=D0=BD=D0=BE=D0=BC=20=D0=BA=D0=BE=D1=88=D0=B5=D0=BB=D1=8C?=
=?UTF-8?q?=D0=BA=D0=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
SHiNE-browser-plugin-wallet/background.js | 171 +++++++++++++++---
SHiNE-browser-plugin-wallet/content-script.js | 13 +-
SHiNE-browser-plugin-wallet/popup.css | 30 +++
SHiNE-browser-plugin-wallet/popup.html | 14 +-
SHiNE-browser-plugin-wallet/popup.js | 73 +++++++-
.../provider-bridge.js | 60 +++++-
VERSION.properties | 4 +-
.../shine_payments/web/admin_tools.html | 16 +-
.../shine_payments/web/buy_ticket.html | 16 +-
.../shine_payments/web/dao_tools.html | 16 +-
.../shine_payments/web/manager_tools.html | 16 +-
.../shine_payments/web/track_ticket.html | 16 +-
12 files changed, 368 insertions(+), 77 deletions(-)
diff --git a/SHiNE-browser-plugin-wallet/background.js b/SHiNE-browser-plugin-wallet/background.js
index dd51774..dd23775 100644
--- a/SHiNE-browser-plugin-wallet/background.js
+++ b/SHiNE-browser-plugin-wallet/background.js
@@ -30,6 +30,7 @@ const state = {
devicesResolvedAtMs: 0,
},
currentWallet: null,
+ pendingApproval: null,
statusText: '',
statusKind: 'info',
};
@@ -69,6 +70,41 @@ function setStatus(message = '', kind = 'info') {
state.statusKind = kind === 'error' ? 'error' : 'info';
}
+function makePendingApprovalSnapshot(payload = {}) {
+ return {
+ id: String(payload?.id || '').trim(),
+ kind: String(payload?.kind || 'sign_transaction').trim() || 'sign_transaction',
+ origin: String(payload?.origin || '').trim(),
+ publicKeyBase58: String(payload?.publicKeyBase58 || '').trim(),
+ comment: String(payload?.comment || '').trim(),
+ createdAtMs: Number(payload?.createdAtMs || Date.now()),
+ transactionSummary: payload?.transactionSummary && typeof payload.transactionSummary === 'object'
+ ? { ...payload.transactionSummary }
+ : null,
+ };
+}
+
+function clearPendingApproval({ rejectError = null } = {}) {
+ if (!state.pendingApproval) return;
+ const pending = state.pendingApproval;
+ state.pendingApproval = null;
+ if (pending.timeoutId) {
+ clearTimeout(pending.timeoutId);
+ }
+ if (rejectError && pending.abortController) {
+ try {
+ pending.abortController.abort(rejectError);
+ } catch {}
+ }
+}
+
+async function openSidePanelForSender(sender) {
+ if (!chrome.sidePanel?.open || !sender?.tab?.id) return;
+ try {
+ await chrome.sidePanel.open({ tabId: sender.tab.id });
+ } catch {}
+}
+
function stopPoll() {
if (state.pollTimer) {
clearTimeout(state.pollTimer);
@@ -506,7 +542,7 @@ async function resolveSelectedHomeserverSession() {
return selectedDevice;
}
-async function callWalletRpc(requestData, timeoutMs = 8000) {
+async function callWalletRpc(requestData, timeoutMs = 8000, abortSignal = null) {
const selectedDevice = await resolveSelectedHomeserverSession();
const resumed = await resumeActiveSession({ keepConnected: true });
if (!resumed.ok) {
@@ -523,22 +559,46 @@ async function callWalletRpc(requestData, timeoutMs = 8000) {
try {
const response = await new Promise((resolve, reject) => {
- const timeoutId = setTimeout(() => {
+ let settled = false;
+ let timeoutId = 0;
+ let off = () => {};
+ let removeAbortListener = () => {};
+ const cleanup = () => {
+ if (settled) return;
+ settled = true;
+ if (timeoutId) clearTimeout(timeoutId);
off();
+ removeAbortListener();
+ };
+ timeoutId = setTimeout(() => {
+ cleanup();
reject(new Error('Таймаут ответа от ESP32.'));
}, timeoutMs);
- const off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
+ off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
const eventPayload = evt?.payload || {};
if (String(eventPayload?.callId || '') !== callId) return;
if (Number(eventPayload?.type || 0) !== WALLET_RPC_RESPONSE_TYPE) return;
- clearTimeout(timeoutId);
- off();
+ cleanup();
try {
resolve(JSON.parse(String(eventPayload?.data || '{}')));
} catch {
reject(new Error('ESP32 вернул некорректный JSON.'));
}
});
+ if (abortSignal) {
+ const onAbort = () => {
+ cleanup();
+ reject(abortSignal.reason instanceof Error ? abortSignal.reason : new Error('Ожидание подписи отменено.'));
+ };
+ if (abortSignal.aborted) {
+ onAbort();
+ return;
+ }
+ abortSignal.addEventListener('abort', onAbort, { once: true });
+ removeAbortListener = () => {
+ abortSignal.removeEventListener('abort', onAbort);
+ };
+ }
ensureApi().callSignalToSession({
toLogin: state.activeSession.login,
targetSessionId: selectedDevice.activeSessionId,
@@ -546,8 +606,7 @@ async function callWalletRpc(requestData, timeoutMs = 8000) {
type: WALLET_RPC_REQUEST_TYPE,
data: JSON.stringify(payload),
}).catch((error) => {
- clearTimeout(timeoutId);
- off();
+ cleanup();
reject(error);
});
});
@@ -606,6 +665,45 @@ async function requestCurrentWallet() {
return { ok: true, wallet: state.currentWallet };
}
+async function cancelPendingSiteApproval() {
+ clearPendingApproval({
+ rejectError: makeCodeError('User canceled request in extension.', 'USER_REJECTED'),
+ });
+ setStatus('Ожидание подписи отменено в расширении.', 'info');
+ return { ok: true };
+}
+
+async function markPendingSiteApprovalResolved() {
+ clearPendingApproval();
+}
+
+async function beginSiteTransactionFlow(payload = {}, sender = null) {
+ if (state.pendingApproval) {
+ throw makeCodeError('Another signing request is already pending.', 'APPROVAL_ALREADY_PENDING');
+ }
+ const abortController = new AbortController();
+ const pending = makePendingApprovalSnapshot({
+ ...payload,
+ kind: 'sign_transaction',
+ id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
+ createdAtMs: Date.now(),
+ });
+ const timeoutId = setTimeout(() => {
+ clearPendingApproval({
+ rejectError: makeCodeError('Signing request timed out in extension.', 'USER_REJECTED'),
+ });
+ setStatus('Ожидание подписи истекло в расширении.', 'error');
+ }, 120000);
+ state.pendingApproval = {
+ ...pending,
+ timeoutId,
+ abortController,
+ };
+ setStatus(`Сайт ${pending.origin} запросил подпись. Подтвердите или отмените на доверенном устройстве.`, 'info');
+ await openSidePanelForSender(sender);
+ return pending;
+}
+
async function siteConnect({ origin, onlyIfTrusted = false } = {}) {
const normalizedOrigin = normalizeOrigin(origin);
if (!normalizedOrigin) {
@@ -636,7 +734,7 @@ async function siteDisconnect({ origin } = {}) {
return { ok: true };
}
-async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment } = {}) {
+async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment, transactionSummary } = {}, sender = null) {
const normalizedOrigin = normalizeOrigin(origin);
if (!normalizedOrigin) {
throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
@@ -649,29 +747,40 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
if (!cleanPub || !cleanTx) {
throw makeCodeError('Transaction payload is incomplete.', 'BAD_REQUEST');
}
+ const pending = await beginSiteTransactionFlow({
+ origin: normalizedOrigin,
+ publicKeyBase58: cleanPub,
+ comment: String(comment || '').trim(),
+ transactionSummary: transactionSummary || null,
+ }, sender);
const requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`;
- const { response } = await callWalletRpc({
- v: 1,
- operation: 'sign_transaction',
- requestId,
- publicKeyBase58: cleanPub,
- transactionBase64: cleanTx,
- comment: signComment,
- }, 120000);
- if (!response?.ok) {
- const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
- if (errorCode === 'REJECTED_BY_USER') {
- throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED');
+ try {
+ const { response } = await callWalletRpc({
+ v: 1,
+ operation: 'sign_transaction',
+ requestId,
+ publicKeyBase58: cleanPub,
+ transactionBase64: cleanTx,
+ comment: signComment,
+ }, 120000, state.pendingApproval?.id === pending.id ? state.pendingApproval.abortController.signal : null);
+ if (!response?.ok) {
+ const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
+ if (errorCode === 'REJECTED_BY_USER') {
+ throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED');
+ }
+ throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED');
}
- throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED');
+ setStatus(`Подпись для ${normalizedOrigin} завершена.`, 'info');
+ return {
+ ok: true,
+ publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
+ signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
+ signatureBase58: String(response?.signatureBase58 || '').trim(),
+ };
+ } finally {
+ await markPendingSiteApprovalResolved();
}
- return {
- ok: true,
- publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
- signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
- signatureBase58: String(response?.signatureBase58 || '').trim(),
- };
}
function snapshot() {
@@ -688,6 +797,7 @@ function snapshot() {
connectionOnline: !!state.activeSession,
walletProfile: state.walletProfile ? { ...state.walletProfile } : null,
currentWallet: state.currentWallet ? { ...state.currentWallet } : null,
+ pendingApproval: state.pendingApproval ? makePendingApprovalSnapshot(state.pendingApproval) : null,
signing: { ...state.signing },
status: {
text: state.statusText,
@@ -749,6 +859,11 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
sendResponse({ ok: true, result, state: snapshot() });
return;
}
+ if (type === 'wallet:cancelPendingSiteApproval') {
+ const result = await cancelPendingSiteApproval();
+ sendResponse({ ok: true, result, state: snapshot() });
+ return;
+ }
if (type === 'wallet:siteConnect') {
const result = await siteConnect(message?.payload || {});
sendResponse({ ok: true, result, state: snapshot() });
@@ -760,7 +875,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
return;
}
if (type === 'wallet:siteSignTransaction') {
- const result = await siteSignTransaction(message?.payload || {});
+ const result = await siteSignTransaction(message?.payload || {}, _sender);
sendResponse({ ok: true, result, state: snapshot() });
return;
}
diff --git a/SHiNE-browser-plugin-wallet/content-script.js b/SHiNE-browser-plugin-wallet/content-script.js
index c2ca1f1..8fa7d79 100644
--- a/SHiNE-browser-plugin-wallet/content-script.js
+++ b/SHiNE-browser-plugin-wallet/content-script.js
@@ -1,5 +1,6 @@
const PAGE_REQUEST = 'shine-wallet-page-request';
const PAGE_RESPONSE = 'shine-wallet-page-response';
+const PAGE_MESSAGE_TARGET_ORIGIN = '*';
function injectProviderBridge() {
const root = document.head || document.documentElement;
@@ -20,14 +21,21 @@ function respondToPage(id, ok, result, error, code) {
result: result || null,
error: error ? String(error) : '',
code: code ? String(code) : '',
- }, window.location.origin);
+ }, PAGE_MESSAGE_TARGET_ORIGIN);
}
function sendRuntimeMessage(type, payload = {}) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ type, payload }, (response) => {
if (chrome.runtime.lastError) {
- reject(new Error(chrome.runtime.lastError.message || 'Runtime message failed'));
+ const raw = String(chrome.runtime.lastError.message || 'Runtime message failed');
+ if (/Extension context invalidated/i.test(raw)) {
+ const error = new Error('Расширение было перезагружено или отключено. Обновите страницу и откройте кошелёк заново.');
+ error.code = 'EXTENSION_CONTEXT_INVALIDATED';
+ reject(error);
+ return;
+ }
+ reject(new Error(raw));
return;
}
if (!response?.ok) {
@@ -71,6 +79,7 @@ window.addEventListener('message', (event) => {
publicKeyBase58: String(params?.publicKeyBase58 || '').trim(),
transactionBase64: String(params?.transactionBase64 || '').trim(),
comment: String(params?.comment || '').trim(),
+ transactionSummary: params?.transactionSummary || null,
});
respondToPage(id, true, response.result || null);
return;
diff --git a/SHiNE-browser-plugin-wallet/popup.css b/SHiNE-browser-plugin-wallet/popup.css
index fe1e0f5..e1b677a 100644
--- a/SHiNE-browser-plugin-wallet/popup.css
+++ b/SHiNE-browser-plugin-wallet/popup.css
@@ -198,6 +198,12 @@ select {
gap: 8px;
}
+.detail-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
.device-row {
padding: 8px 10px;
border: 1px solid #243446;
@@ -205,6 +211,30 @@ select {
background: #0d141d;
}
+.detail-row {
+ display: grid;
+ gap: 4px;
+ padding: 8px 10px;
+ border: 1px solid #243446;
+ border-radius: 8px;
+ background: #0d141d;
+}
+
+.detail-label {
+ font-size: 12px;
+ color: #9aabbd;
+}
+
+.detail-value {
+ color: #e8eef6;
+ word-break: break-word;
+}
+
+.detail-value.mono {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ color: #bed5f5;
+}
+
.device-state {
font-size: 12px;
text-transform: lowercase;
diff --git a/SHiNE-browser-plugin-wallet/popup.html b/SHiNE-browser-plugin-wallet/popup.html
index 773a2ae..41cf9e4 100644
--- a/SHiNE-browser-plugin-wallet/popup.html
+++ b/SHiNE-browser-plugin-wallet/popup.html
@@ -18,7 +18,6 @@
Сервер SHiNE: —
- Адрес: —
Подключение
@@ -50,9 +49,6 @@
Подключено
Логин—
-
Session—
-
Типwallet
-
clientKey—
@@ -72,6 +68,16 @@
+
+
Ожидается подпись
+
Сайт запросил подписание транзакции.
+
+
Запрос уже отправлен на доверенное устройство. Здесь можно только отменить ожидание.
+
+
+
+
+
Полученный кошелёк
Тип—
diff --git a/SHiNE-browser-plugin-wallet/popup.js b/SHiNE-browser-plugin-wallet/popup.js
index 655f74c..e7f7d60 100644
--- a/SHiNE-browser-plugin-wallet/popup.js
+++ b/SHiNE-browser-plugin-wallet/popup.js
@@ -2,7 +2,6 @@ import { formatPairingShortCode } from './js/lib/device-pairing.js';
const els = {
serverLoginInfo: document.querySelector('#server-login-info'),
- serverAddress: document.querySelector('#server-address'),
loginInput: document.querySelector('#login-input'),
usePassword: document.querySelector('#use-password'),
passwordField: document.querySelector('#password-field'),
@@ -17,9 +16,6 @@ const els = {
status: document.querySelector('#status'),
sessionCard: document.querySelector('#session-card'),
sessionLogin: document.querySelector('#session-login'),
- sessionId: document.querySelector('#session-id'),
- sessionType: document.querySelector('#session-type'),
- clientKeyShort: document.querySelector('#client-key-short'),
resumeBtn: document.querySelector('#resume-btn'),
refreshDevicesBtn: document.querySelector('#refresh-devices-btn'),
disconnectBtn: document.querySelector('#disconnect-btn'),
@@ -27,6 +23,10 @@ const els = {
deviceSelect: document.querySelector('#device-select'),
homeserverList: document.querySelector('#homeserver-list'),
requestWalletBtn: document.querySelector('#request-wallet-btn'),
+ pendingApprovalCard: document.querySelector('#pending-approval-card'),
+ pendingApprovalSubtitle: document.querySelector('#pending-approval-subtitle'),
+ pendingApprovalDetails: document.querySelector('#pending-approval-details'),
+ cancelPendingApprovalBtn: document.querySelector('#cancel-pending-approval-btn'),
walletResultCard: document.querySelector('#wallet-result-card'),
walletType: document.querySelector('#wallet-type'),
walletPubkey: document.querySelector('#wallet-pubkey'),
@@ -52,6 +52,7 @@ let state = {
selectedDeviceName: '',
},
currentWallet: null,
+ pendingApproval: null,
status: {
text: '',
kind: 'info',
@@ -107,13 +108,49 @@ function renderHomeserverList(items = []) {
});
}
+function renderPendingApproval(pendingApproval) {
+ els.pendingApprovalDetails.innerHTML = '';
+ if (!pendingApproval) return;
+ const summary = pendingApproval.transactionSummary || {};
+ const programs = Array.isArray(summary.programs) && summary.programs.length
+ ? summary.programs.join(', ')
+ : 'не определены';
+ const details = [
+ { label: 'Сайт', value: pendingApproval.origin || '—', mono: true },
+ { label: 'Кошелёк', value: pendingApproval.publicKeyBase58 || '—', mono: true },
+ { label: 'Комментарий', value: pendingApproval.comment || 'Транзакция запрошена сайтом' },
+ { label: 'Тип', value: summary.kind || 'legacy' },
+ { label: 'Инструкций', value: String(summary.instructionCount ?? 0) },
+ { label: 'Программы', value: programs, mono: true },
+ ];
+ if (summary.feePayer) {
+ details.push({ label: 'Fee payer', value: summary.feePayer, mono: true });
+ }
+ if (summary.recentBlockhash) {
+ details.push({ label: 'Blockhash', value: summary.recentBlockhash, mono: true });
+ }
+ for (const item of details) {
+ const row = document.createElement('div');
+ row.className = 'detail-row';
+ const label = document.createElement('div');
+ label.className = 'detail-label';
+ label.textContent = item.label;
+ const value = document.createElement('div');
+ value.className = `detail-value${item.mono ? ' mono' : ''}`;
+ value.textContent = item.value;
+ row.append(label, value);
+ els.pendingApprovalDetails.append(row);
+ }
+}
+
function applyState(nextState) {
state = nextState || state;
const loginValue = String(state?.settings?.login || '');
const resolvedServerLogin = String(state?.settings?.serverLogin || '').trim();
const resolvedServerAddress = String(state?.settings?.serverHttp || '').trim();
- els.serverLoginInfo.textContent = resolvedServerLogin ? `Сервер SHiNE: ${resolvedServerLogin}` : 'Сервер SHiNE: —';
- els.serverAddress.textContent = resolvedServerAddress ? `Адрес: ${resolvedServerAddress}` : 'Адрес: —';
+ els.serverLoginInfo.textContent = resolvedServerLogin && resolvedServerAddress
+ ? `Сервер SHiNE: ${resolvedServerLogin} (${resolvedServerAddress})`
+ : 'Сервер SHiNE: —';
if (document.activeElement !== els.loginInput) {
els.loginInput.value = loginValue;
}
@@ -125,16 +162,15 @@ function applyState(nextState) {
const walletProfile = state?.walletProfile;
const signing = state?.signing || {};
const currentWallet = state?.currentWallet || null;
+ const pendingApproval = state?.pendingApproval || null;
els.connectCard.classList.toggle('hidden', !!session);
els.sessionCard.classList.toggle('hidden', !session);
els.walletCard.classList.toggle('hidden', !session);
+ els.pendingApprovalCard.classList.toggle('hidden', !pendingApproval);
if (session) {
els.sessionLogin.textContent = session.login || '—';
- els.sessionId.textContent = session.sessionId || '—';
- els.sessionType.textContent = String(session.sessionType || 50) === '50' ? 'wallet' : String(session.sessionType || '—');
- els.clientKeyShort.textContent = shortKey(walletProfile?.publicKeys?.clientKeyBase58 || '');
}
const homeservers = Array.isArray(walletProfile?.homeserverSessions) ? walletProfile.homeserverSessions : [];
@@ -149,6 +185,16 @@ function applyState(nextState) {
renderHomeserverList(homeservers);
els.requestWalletBtn.disabled = !session || !signing.selectedDeviceName;
+ if (pendingApproval) {
+ els.pendingApprovalSubtitle.textContent = pendingApproval.origin
+ ? `Сайт ${pendingApproval.origin} запросил подписание транзакции.`
+ : 'Сайт запросил подписание транзакции.';
+ renderPendingApproval(pendingApproval);
+ } else {
+ els.pendingApprovalSubtitle.textContent = 'Сайт запросил подписание транзакции.';
+ els.pendingApprovalDetails.innerHTML = '';
+ }
+
if (currentWallet?.publicKeyBase58) {
els.walletResultCard.classList.remove('hidden');
els.walletType.textContent = currentWallet.type || '—';
@@ -313,6 +359,14 @@ async function copyWalletKey() {
}
}
+async function cancelPendingApproval() {
+ try {
+ await sendMessage('wallet:cancelPendingSiteApproval');
+ } catch (error) {
+ setStatus(error.message || 'Не удалось отменить ожидание подписи.', 'error');
+ }
+}
+
function startUiRefreshLoop() {
stopUiRefreshLoop();
refreshTimer = window.setInterval(() => {
@@ -347,6 +401,7 @@ function bindUi() {
els.deviceSelect.addEventListener('change', () => { void updateDeviceSelection(); });
els.requestWalletBtn.addEventListener('click', () => { void requestCurrentWallet(); });
els.copyWalletBtn.addEventListener('click', () => { void copyWalletKey(); });
+ els.cancelPendingApprovalBtn.addEventListener('click', () => { void cancelPendingApproval(); });
}
async function init() {
diff --git a/SHiNE-browser-plugin-wallet/provider-bridge.js b/SHiNE-browser-plugin-wallet/provider-bridge.js
index b19527c..d6ad649 100644
--- a/SHiNE-browser-plugin-wallet/provider-bridge.js
+++ b/SHiNE-browser-plugin-wallet/provider-bridge.js
@@ -2,6 +2,7 @@ import { PublicKey, Transaction, VersionedTransaction } from './js/lib/vendor/so
const PAGE_REQUEST = 'shine-wallet-page-request';
const PAGE_RESPONSE = 'shine-wallet-page-response';
+const PAGE_MESSAGE_TARGET_ORIGIN = '*';
const STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet';
const STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready';
const SOLANA_CHAINS = ['solana:mainnet', 'solana:devnet', 'solana:testnet'];
@@ -39,6 +40,45 @@ function createProviderError(message, code = '') {
return error;
}
+function summarizeTransaction(transaction) {
+ const summary = {
+ kind: 'legacy',
+ instructionCount: 0,
+ accountCount: 0,
+ feePayer: '',
+ recentBlockhash: '',
+ programs: [],
+ };
+ if (!transaction) return summary;
+
+ const isVersioned = typeof transaction?.version === 'number' || transaction instanceof VersionedTransaction;
+ summary.kind = isVersioned ? `versioned:${String(transaction.version)}` : 'legacy';
+ summary.feePayer = String(transaction?.feePayer?.toBase58?.() || '').trim();
+ summary.recentBlockhash = String(transaction?.recentBlockhash || transaction?.message?.recentBlockhash || '').trim();
+
+ if (isVersioned) {
+ const message = transaction?.message || {};
+ const staticKeys = Array.isArray(message?.staticAccountKeys) ? message.staticAccountKeys : [];
+ const instructions = Array.isArray(message?.compiledInstructions) ? message.compiledInstructions : [];
+ summary.instructionCount = instructions.length;
+ summary.accountCount = staticKeys.length;
+ summary.programs = instructions
+ .map((instruction) => staticKeys[instruction?.programIdIndex]?.toBase58?.() || '')
+ .filter(Boolean)
+ .slice(0, 5);
+ return summary;
+ }
+
+ const instructions = Array.isArray(transaction?.instructions) ? transaction.instructions : [];
+ summary.instructionCount = instructions.length;
+ summary.accountCount = Array.isArray(transaction?.signatures) ? transaction.signatures.length : 0;
+ summary.programs = instructions
+ .map((instruction) => instruction?.programId?.toBase58?.() || '')
+ .filter(Boolean)
+ .slice(0, 5);
+ return summary;
+}
+
function createRequest(method, params = {}) {
const id = `shine-wallet-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
return new Promise((resolve, reject) => {
@@ -59,7 +99,7 @@ function createRequest(method, params = {}) {
id,
method,
params,
- }, window.location.origin);
+ }, PAGE_MESSAGE_TARGET_ORIGIN);
});
}
@@ -122,12 +162,6 @@ class ShineProviderCore {
async connect(options = {}) {
const onlyIfTrusted = !!options?.onlyIfTrusted || !!options?.silent;
- if (!onlyIfTrusted) {
- const confirmed = window.confirm(`Connect SHiNE Wallet to ${window.location.origin}?`);
- if (!confirmed) {
- throw createProviderError('User rejected wallet connection', 'USER_REJECTED');
- }
- }
const result = await createRequest('connect', { onlyIfTrusted });
const nextKey = new PublicKey(String(result?.publicKeyBase58 || '').trim());
this.publicKey = nextKey;
@@ -157,10 +191,12 @@ class ShineProviderCore {
await this.connect();
}
const transactionBase64 = serializeTransactionBase64(transaction);
+ const transactionSummary = summarizeTransaction(transaction);
const result = await createRequest('signTransaction', {
publicKeyBase58: this.publicKeyBase58,
transactionBase64,
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
+ transactionSummary,
});
return deserializeSignedTransaction(String(result?.signedTransactionBase64 || ''), transaction);
}
@@ -169,10 +205,20 @@ class ShineProviderCore {
if (!this.publicKey) {
await this.connect();
}
+ const transactionSummary = {
+ kind: 'raw-bytes',
+ instructionCount: 0,
+ accountCount: 0,
+ feePayer: this.publicKeyBase58,
+ recentBlockhash: '',
+ programs: [],
+ byteLength: Number(transactionBytes?.length || 0),
+ };
const result = await createRequest('signTransaction', {
publicKeyBase58: this.publicKeyBase58,
transactionBase64: bytesToBase64(transactionBytes),
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
+ transactionSummary,
});
return base64ToBytes(String(result?.signedTransactionBase64 || '').trim());
}
diff --git a/VERSION.properties b/VERSION.properties
index 53f4e1f..be130be 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.2.258
-server.version=1.2.243
+client.version=1.2.259
+server.version=1.2.244
diff --git a/shine-solana/shine/programs/shine_payments/web/admin_tools.html b/shine-solana/shine/programs/shine_payments/web/admin_tools.html
index 9dd6370..526aa4d 100644
--- a/shine-solana/shine/programs/shine_payments/web/admin_tools.html
+++ b/shine-solana/shine/programs/shine_payments/web/admin_tools.html
@@ -240,11 +240,17 @@
return window.solana;
}
async function connectWallet() {
- const provider = getProvider();
- const r = await provider.connect();
- walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
- document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
- await refreshAll();
+ const out = document.getElementById("walletInfo");
+ try {
+ const provider = getProvider();
+ const r = await provider.connect();
+ walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
+ out.textContent = "Кошелек: " + walletPubkey.toBase58();
+ await refreshAll();
+ } catch (e) {
+ out.innerHTML = `
${String(e?.message || e)}`;
+ throw e;
+ }
}
async function sendInstruction(ix) {
const provider = getProvider();
diff --git a/shine-solana/shine/programs/shine_payments/web/buy_ticket.html b/shine-solana/shine/programs/shine_payments/web/buy_ticket.html
index 7911626..2794cad 100644
--- a/shine-solana/shine/programs/shine_payments/web/buy_ticket.html
+++ b/shine-solana/shine/programs/shine_payments/web/buy_ticket.html
@@ -221,11 +221,17 @@
}
async function connectWallet() {
- const provider = getProvider();
- const r = await provider.connect();
- walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
- document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
- await refreshState();
+ const out = document.getElementById("walletInfo");
+ try {
+ const provider = getProvider();
+ const r = await provider.connect();
+ walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
+ out.textContent = "Кошелек: " + walletPubkey.toBase58();
+ await refreshState();
+ } catch (e) {
+ out.innerHTML = `
${String(e?.message || e)}`;
+ throw e;
+ }
}
async function sendInstruction(ix) {
diff --git a/shine-solana/shine/programs/shine_payments/web/dao_tools.html b/shine-solana/shine/programs/shine_payments/web/dao_tools.html
index 4e3099e..38d07d9 100644
--- a/shine-solana/shine/programs/shine_payments/web/dao_tools.html
+++ b/shine-solana/shine/programs/shine_payments/web/dao_tools.html
@@ -156,11 +156,17 @@
return window.solana;
}
async function connectWallet() {
- const provider = getProvider();
- const r = await provider.connect();
- walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
- document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
- await refresh();
+ const out = document.getElementById("walletInfo");
+ try {
+ const provider = getProvider();
+ const r = await provider.connect();
+ walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
+ out.textContent = "Кошелек: " + walletPubkey.toBase58();
+ await refresh();
+ } catch (e) {
+ out.innerHTML = `
${String(e?.message || e)}`;
+ throw e;
+ }
}
async function sendInstruction(ix) {
const provider = getProvider();
diff --git a/shine-solana/shine/programs/shine_payments/web/manager_tools.html b/shine-solana/shine/programs/shine_payments/web/manager_tools.html
index 479956c..c9e5b9c 100644
--- a/shine-solana/shine/programs/shine_payments/web/manager_tools.html
+++ b/shine-solana/shine/programs/shine_payments/web/manager_tools.html
@@ -162,11 +162,17 @@
return window.solana;
}
async function connectWallet() {
- const provider = getProvider();
- const r = await provider.connect();
- walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
- document.getElementById("walletInfo").textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
- await refresh();
+ const out = document.getElementById("walletInfo");
+ try {
+ const provider = getProvider();
+ const r = await provider.connect();
+ walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
+ out.textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
+ await refresh();
+ } catch (e) {
+ out.innerHTML = `
${String(e?.message || e)}`;
+ throw e;
+ }
}
async function sendInstruction(ix) {
const provider = getProvider();
diff --git a/shine-solana/shine/programs/shine_payments/web/track_ticket.html b/shine-solana/shine/programs/shine_payments/web/track_ticket.html
index 06c73db..23c8692 100644
--- a/shine-solana/shine/programs/shine_payments/web/track_ticket.html
+++ b/shine-solana/shine/programs/shine_payments/web/track_ticket.html
@@ -210,11 +210,17 @@
return window.solana;
}
async function connectWallet() {
- const provider = getProvider();
- const r = await provider.connect();
- walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
- document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
- await refreshAll();
+ const out = document.getElementById("walletInfo");
+ try {
+ const provider = getProvider();
+ const r = await provider.connect();
+ walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
+ out.textContent = "Кошелек: " + walletPubkey.toBase58();
+ await refreshAll();
+ } catch (e) {
+ out.innerHTML = `
${String(e?.message || e)}`;
+ throw e;
+ }
}
async function sendInstruction(ix) {
const provider = getProvider();