Исправить подключение и подпись в браузерном кошельке
This commit is contained in:
parent
23e61cc182
commit
684f3237cf
@ -30,6 +30,7 @@ const state = {
|
|||||||
devicesResolvedAtMs: 0,
|
devicesResolvedAtMs: 0,
|
||||||
},
|
},
|
||||||
currentWallet: null,
|
currentWallet: null,
|
||||||
|
pendingApproval: null,
|
||||||
statusText: '',
|
statusText: '',
|
||||||
statusKind: 'info',
|
statusKind: 'info',
|
||||||
};
|
};
|
||||||
@ -69,6 +70,41 @@ function setStatus(message = '', kind = 'info') {
|
|||||||
state.statusKind = kind === 'error' ? 'error' : '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() {
|
function stopPoll() {
|
||||||
if (state.pollTimer) {
|
if (state.pollTimer) {
|
||||||
clearTimeout(state.pollTimer);
|
clearTimeout(state.pollTimer);
|
||||||
@ -506,7 +542,7 @@ async function resolveSelectedHomeserverSession() {
|
|||||||
return selectedDevice;
|
return selectedDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callWalletRpc(requestData, timeoutMs = 8000) {
|
async function callWalletRpc(requestData, timeoutMs = 8000, abortSignal = null) {
|
||||||
const selectedDevice = await resolveSelectedHomeserverSession();
|
const selectedDevice = await resolveSelectedHomeserverSession();
|
||||||
const resumed = await resumeActiveSession({ keepConnected: true });
|
const resumed = await resumeActiveSession({ keepConnected: true });
|
||||||
if (!resumed.ok) {
|
if (!resumed.ok) {
|
||||||
@ -523,22 +559,46 @@ async function callWalletRpc(requestData, timeoutMs = 8000) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await new Promise((resolve, reject) => {
|
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();
|
off();
|
||||||
|
removeAbortListener();
|
||||||
|
};
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
reject(new Error('Таймаут ответа от ESP32.'));
|
reject(new Error('Таймаут ответа от ESP32.'));
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
const off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
|
off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
|
||||||
const eventPayload = evt?.payload || {};
|
const eventPayload = evt?.payload || {};
|
||||||
if (String(eventPayload?.callId || '') !== callId) return;
|
if (String(eventPayload?.callId || '') !== callId) return;
|
||||||
if (Number(eventPayload?.type || 0) !== WALLET_RPC_RESPONSE_TYPE) return;
|
if (Number(eventPayload?.type || 0) !== WALLET_RPC_RESPONSE_TYPE) return;
|
||||||
clearTimeout(timeoutId);
|
cleanup();
|
||||||
off();
|
|
||||||
try {
|
try {
|
||||||
resolve(JSON.parse(String(eventPayload?.data || '{}')));
|
resolve(JSON.parse(String(eventPayload?.data || '{}')));
|
||||||
} catch {
|
} catch {
|
||||||
reject(new Error('ESP32 вернул некорректный JSON.'));
|
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({
|
ensureApi().callSignalToSession({
|
||||||
toLogin: state.activeSession.login,
|
toLogin: state.activeSession.login,
|
||||||
targetSessionId: selectedDevice.activeSessionId,
|
targetSessionId: selectedDevice.activeSessionId,
|
||||||
@ -546,8 +606,7 @@ async function callWalletRpc(requestData, timeoutMs = 8000) {
|
|||||||
type: WALLET_RPC_REQUEST_TYPE,
|
type: WALLET_RPC_REQUEST_TYPE,
|
||||||
data: JSON.stringify(payload),
|
data: JSON.stringify(payload),
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
clearTimeout(timeoutId);
|
cleanup();
|
||||||
off();
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -606,6 +665,45 @@ async function requestCurrentWallet() {
|
|||||||
return { ok: true, wallet: state.currentWallet };
|
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 } = {}) {
|
async function siteConnect({ origin, onlyIfTrusted = false } = {}) {
|
||||||
const normalizedOrigin = normalizeOrigin(origin);
|
const normalizedOrigin = normalizeOrigin(origin);
|
||||||
if (!normalizedOrigin) {
|
if (!normalizedOrigin) {
|
||||||
@ -636,7 +734,7 @@ async function siteDisconnect({ origin } = {}) {
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment } = {}) {
|
async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment, transactionSummary } = {}, sender = null) {
|
||||||
const normalizedOrigin = normalizeOrigin(origin);
|
const normalizedOrigin = normalizeOrigin(origin);
|
||||||
if (!normalizedOrigin) {
|
if (!normalizedOrigin) {
|
||||||
throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
|
throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
|
||||||
@ -649,8 +747,15 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
|
|||||||
if (!cleanPub || !cleanTx) {
|
if (!cleanPub || !cleanTx) {
|
||||||
throw makeCodeError('Transaction payload is incomplete.', 'BAD_REQUEST');
|
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 requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
||||||
const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`;
|
const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`;
|
||||||
|
try {
|
||||||
const { response } = await callWalletRpc({
|
const { response } = await callWalletRpc({
|
||||||
v: 1,
|
v: 1,
|
||||||
operation: 'sign_transaction',
|
operation: 'sign_transaction',
|
||||||
@ -658,7 +763,7 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
|
|||||||
publicKeyBase58: cleanPub,
|
publicKeyBase58: cleanPub,
|
||||||
transactionBase64: cleanTx,
|
transactionBase64: cleanTx,
|
||||||
comment: signComment,
|
comment: signComment,
|
||||||
}, 120000);
|
}, 120000, state.pendingApproval?.id === pending.id ? state.pendingApproval.abortController.signal : null);
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
|
const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
|
||||||
if (errorCode === 'REJECTED_BY_USER') {
|
if (errorCode === 'REJECTED_BY_USER') {
|
||||||
@ -666,12 +771,16 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
|
|||||||
}
|
}
|
||||||
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 {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
|
publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
|
||||||
signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
|
signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
|
||||||
signatureBase58: String(response?.signatureBase58 || '').trim(),
|
signatureBase58: String(response?.signatureBase58 || '').trim(),
|
||||||
};
|
};
|
||||||
|
} finally {
|
||||||
|
await markPendingSiteApprovalResolved();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
@ -688,6 +797,7 @@ function snapshot() {
|
|||||||
connectionOnline: !!state.activeSession,
|
connectionOnline: !!state.activeSession,
|
||||||
walletProfile: state.walletProfile ? { ...state.walletProfile } : null,
|
walletProfile: state.walletProfile ? { ...state.walletProfile } : null,
|
||||||
currentWallet: state.currentWallet ? { ...state.currentWallet } : null,
|
currentWallet: state.currentWallet ? { ...state.currentWallet } : null,
|
||||||
|
pendingApproval: state.pendingApproval ? makePendingApprovalSnapshot(state.pendingApproval) : null,
|
||||||
signing: { ...state.signing },
|
signing: { ...state.signing },
|
||||||
status: {
|
status: {
|
||||||
text: state.statusText,
|
text: state.statusText,
|
||||||
@ -749,6 +859,11 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
sendResponse({ ok: true, result, state: snapshot() });
|
sendResponse({ ok: true, result, state: snapshot() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (type === 'wallet:cancelPendingSiteApproval') {
|
||||||
|
const result = await cancelPendingSiteApproval();
|
||||||
|
sendResponse({ ok: true, result, state: snapshot() });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (type === 'wallet:siteConnect') {
|
if (type === 'wallet:siteConnect') {
|
||||||
const result = await siteConnect(message?.payload || {});
|
const result = await siteConnect(message?.payload || {});
|
||||||
sendResponse({ ok: true, result, state: snapshot() });
|
sendResponse({ ok: true, result, state: snapshot() });
|
||||||
@ -760,7 +875,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === 'wallet:siteSignTransaction') {
|
if (type === 'wallet:siteSignTransaction') {
|
||||||
const result = await siteSignTransaction(message?.payload || {});
|
const result = await siteSignTransaction(message?.payload || {}, _sender);
|
||||||
sendResponse({ ok: true, result, state: snapshot() });
|
sendResponse({ ok: true, result, state: snapshot() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
const PAGE_REQUEST = 'shine-wallet-page-request';
|
const PAGE_REQUEST = 'shine-wallet-page-request';
|
||||||
const PAGE_RESPONSE = 'shine-wallet-page-response';
|
const PAGE_RESPONSE = 'shine-wallet-page-response';
|
||||||
|
const PAGE_MESSAGE_TARGET_ORIGIN = '*';
|
||||||
|
|
||||||
function injectProviderBridge() {
|
function injectProviderBridge() {
|
||||||
const root = document.head || document.documentElement;
|
const root = document.head || document.documentElement;
|
||||||
@ -20,14 +21,21 @@ function respondToPage(id, ok, result, error, code) {
|
|||||||
result: result || null,
|
result: result || null,
|
||||||
error: error ? String(error) : '',
|
error: error ? String(error) : '',
|
||||||
code: code ? String(code) : '',
|
code: code ? String(code) : '',
|
||||||
}, window.location.origin);
|
}, PAGE_MESSAGE_TARGET_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendRuntimeMessage(type, payload = {}) {
|
function sendRuntimeMessage(type, payload = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
chrome.runtime.sendMessage({ type, payload }, (response) => {
|
chrome.runtime.sendMessage({ type, payload }, (response) => {
|
||||||
if (chrome.runtime.lastError) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
@ -71,6 +79,7 @@ window.addEventListener('message', (event) => {
|
|||||||
publicKeyBase58: String(params?.publicKeyBase58 || '').trim(),
|
publicKeyBase58: String(params?.publicKeyBase58 || '').trim(),
|
||||||
transactionBase64: String(params?.transactionBase64 || '').trim(),
|
transactionBase64: String(params?.transactionBase64 || '').trim(),
|
||||||
comment: String(params?.comment || '').trim(),
|
comment: String(params?.comment || '').trim(),
|
||||||
|
transactionSummary: params?.transactionSummary || null,
|
||||||
});
|
});
|
||||||
respondToPage(id, true, response.result || null);
|
respondToPage(id, true, response.result || null);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -198,6 +198,12 @@ select {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.device-row {
|
.device-row {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border: 1px solid #243446;
|
border: 1px solid #243446;
|
||||||
@ -205,6 +211,30 @@ select {
|
|||||||
background: #0d141d;
|
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 {
|
.device-state {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="server-login-info" class="muted small">Сервер SHiNE: —</p>
|
<p id="server-login-info" class="muted small">Сервер SHiNE: —</p>
|
||||||
<p id="server-address" class="muted small">Адрес: —</p>
|
|
||||||
|
|
||||||
<div id="connect-card" class="card">
|
<div id="connect-card" class="card">
|
||||||
<div class="card-title">Подключение</div>
|
<div class="card-title">Подключение</div>
|
||||||
@ -50,9 +49,6 @@
|
|||||||
<div id="session-card" class="card hidden">
|
<div id="session-card" class="card hidden">
|
||||||
<div class="card-title">Подключено</div>
|
<div class="card-title">Подключено</div>
|
||||||
<div class="summary-row"><span>Логин</span><strong id="session-login">—</strong></div>
|
<div class="summary-row"><span>Логин</span><strong id="session-login">—</strong></div>
|
||||||
<div class="summary-row"><span>Session</span><code id="session-id">—</code></div>
|
|
||||||
<div class="summary-row"><span>Тип</span><strong id="session-type">wallet</strong></div>
|
|
||||||
<div class="summary-row"><span>clientKey</span><code id="client-key-short">—</code></div>
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="resume-btn" class="btn secondary" type="button">Проверить session</button>
|
<button id="resume-btn" class="btn secondary" type="button">Проверить session</button>
|
||||||
<button id="disconnect-btn" class="btn danger" type="button">Отключить</button>
|
<button id="disconnect-btn" class="btn danger" type="button">Отключить</button>
|
||||||
@ -72,6 +68,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="pending-approval-card" class="card hidden">
|
||||||
|
<div class="card-title">Ожидается подпись</div>
|
||||||
|
<p id="pending-approval-subtitle" class="muted small">Сайт запросил подписание транзакции.</p>
|
||||||
|
<div id="pending-approval-details" class="device-list"></div>
|
||||||
|
<p class="muted small">Запрос уже отправлен на доверенное устройство. Здесь можно только отменить ожидание.</p>
|
||||||
|
<div class="actions">
|
||||||
|
<button id="cancel-pending-approval-btn" class="btn danger" type="button">Отменить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="wallet-result-card" class="card hidden">
|
<div id="wallet-result-card" class="card hidden">
|
||||||
<div class="card-title">Полученный кошелёк</div>
|
<div class="card-title">Полученный кошелёк</div>
|
||||||
<div class="summary-row"><span>Тип</span><strong id="wallet-type">—</strong></div>
|
<div class="summary-row"><span>Тип</span><strong id="wallet-type">—</strong></div>
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { formatPairingShortCode } from './js/lib/device-pairing.js';
|
|||||||
|
|
||||||
const els = {
|
const els = {
|
||||||
serverLoginInfo: document.querySelector('#server-login-info'),
|
serverLoginInfo: document.querySelector('#server-login-info'),
|
||||||
serverAddress: document.querySelector('#server-address'),
|
|
||||||
loginInput: document.querySelector('#login-input'),
|
loginInput: document.querySelector('#login-input'),
|
||||||
usePassword: document.querySelector('#use-password'),
|
usePassword: document.querySelector('#use-password'),
|
||||||
passwordField: document.querySelector('#password-field'),
|
passwordField: document.querySelector('#password-field'),
|
||||||
@ -17,9 +16,6 @@ const els = {
|
|||||||
status: document.querySelector('#status'),
|
status: document.querySelector('#status'),
|
||||||
sessionCard: document.querySelector('#session-card'),
|
sessionCard: document.querySelector('#session-card'),
|
||||||
sessionLogin: document.querySelector('#session-login'),
|
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'),
|
resumeBtn: document.querySelector('#resume-btn'),
|
||||||
refreshDevicesBtn: document.querySelector('#refresh-devices-btn'),
|
refreshDevicesBtn: document.querySelector('#refresh-devices-btn'),
|
||||||
disconnectBtn: document.querySelector('#disconnect-btn'),
|
disconnectBtn: document.querySelector('#disconnect-btn'),
|
||||||
@ -27,6 +23,10 @@ const els = {
|
|||||||
deviceSelect: document.querySelector('#device-select'),
|
deviceSelect: document.querySelector('#device-select'),
|
||||||
homeserverList: document.querySelector('#homeserver-list'),
|
homeserverList: document.querySelector('#homeserver-list'),
|
||||||
requestWalletBtn: document.querySelector('#request-wallet-btn'),
|
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'),
|
walletResultCard: document.querySelector('#wallet-result-card'),
|
||||||
walletType: document.querySelector('#wallet-type'),
|
walletType: document.querySelector('#wallet-type'),
|
||||||
walletPubkey: document.querySelector('#wallet-pubkey'),
|
walletPubkey: document.querySelector('#wallet-pubkey'),
|
||||||
@ -52,6 +52,7 @@ let state = {
|
|||||||
selectedDeviceName: '',
|
selectedDeviceName: '',
|
||||||
},
|
},
|
||||||
currentWallet: null,
|
currentWallet: null,
|
||||||
|
pendingApproval: null,
|
||||||
status: {
|
status: {
|
||||||
text: '',
|
text: '',
|
||||||
kind: 'info',
|
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) {
|
function applyState(nextState) {
|
||||||
state = nextState || state;
|
state = nextState || state;
|
||||||
const loginValue = String(state?.settings?.login || '');
|
const loginValue = String(state?.settings?.login || '');
|
||||||
const resolvedServerLogin = String(state?.settings?.serverLogin || '').trim();
|
const resolvedServerLogin = String(state?.settings?.serverLogin || '').trim();
|
||||||
const resolvedServerAddress = String(state?.settings?.serverHttp || '').trim();
|
const resolvedServerAddress = String(state?.settings?.serverHttp || '').trim();
|
||||||
els.serverLoginInfo.textContent = resolvedServerLogin ? `Сервер SHiNE: ${resolvedServerLogin}` : 'Сервер SHiNE: —';
|
els.serverLoginInfo.textContent = resolvedServerLogin && resolvedServerAddress
|
||||||
els.serverAddress.textContent = resolvedServerAddress ? `Адрес: ${resolvedServerAddress}` : 'Адрес: —';
|
? `Сервер SHiNE: ${resolvedServerLogin} (${resolvedServerAddress})`
|
||||||
|
: 'Сервер SHiNE: —';
|
||||||
if (document.activeElement !== els.loginInput) {
|
if (document.activeElement !== els.loginInput) {
|
||||||
els.loginInput.value = loginValue;
|
els.loginInput.value = loginValue;
|
||||||
}
|
}
|
||||||
@ -125,16 +162,15 @@ function applyState(nextState) {
|
|||||||
const walletProfile = state?.walletProfile;
|
const walletProfile = state?.walletProfile;
|
||||||
const signing = state?.signing || {};
|
const signing = state?.signing || {};
|
||||||
const currentWallet = state?.currentWallet || null;
|
const currentWallet = state?.currentWallet || null;
|
||||||
|
const pendingApproval = state?.pendingApproval || null;
|
||||||
|
|
||||||
els.connectCard.classList.toggle('hidden', !!session);
|
els.connectCard.classList.toggle('hidden', !!session);
|
||||||
els.sessionCard.classList.toggle('hidden', !session);
|
els.sessionCard.classList.toggle('hidden', !session);
|
||||||
els.walletCard.classList.toggle('hidden', !session);
|
els.walletCard.classList.toggle('hidden', !session);
|
||||||
|
els.pendingApprovalCard.classList.toggle('hidden', !pendingApproval);
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
els.sessionLogin.textContent = session.login || '—';
|
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 : [];
|
const homeservers = Array.isArray(walletProfile?.homeserverSessions) ? walletProfile.homeserverSessions : [];
|
||||||
@ -149,6 +185,16 @@ function applyState(nextState) {
|
|||||||
renderHomeserverList(homeservers);
|
renderHomeserverList(homeservers);
|
||||||
els.requestWalletBtn.disabled = !session || !signing.selectedDeviceName;
|
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) {
|
if (currentWallet?.publicKeyBase58) {
|
||||||
els.walletResultCard.classList.remove('hidden');
|
els.walletResultCard.classList.remove('hidden');
|
||||||
els.walletType.textContent = currentWallet.type || '—';
|
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() {
|
function startUiRefreshLoop() {
|
||||||
stopUiRefreshLoop();
|
stopUiRefreshLoop();
|
||||||
refreshTimer = window.setInterval(() => {
|
refreshTimer = window.setInterval(() => {
|
||||||
@ -347,6 +401,7 @@ function bindUi() {
|
|||||||
els.deviceSelect.addEventListener('change', () => { void updateDeviceSelection(); });
|
els.deviceSelect.addEventListener('change', () => { void updateDeviceSelection(); });
|
||||||
els.requestWalletBtn.addEventListener('click', () => { void requestCurrentWallet(); });
|
els.requestWalletBtn.addEventListener('click', () => { void requestCurrentWallet(); });
|
||||||
els.copyWalletBtn.addEventListener('click', () => { void copyWalletKey(); });
|
els.copyWalletBtn.addEventListener('click', () => { void copyWalletKey(); });
|
||||||
|
els.cancelPendingApprovalBtn.addEventListener('click', () => { void cancelPendingApproval(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { PublicKey, Transaction, VersionedTransaction } from './js/lib/vendor/so
|
|||||||
|
|
||||||
const PAGE_REQUEST = 'shine-wallet-page-request';
|
const PAGE_REQUEST = 'shine-wallet-page-request';
|
||||||
const PAGE_RESPONSE = 'shine-wallet-page-response';
|
const PAGE_RESPONSE = 'shine-wallet-page-response';
|
||||||
|
const PAGE_MESSAGE_TARGET_ORIGIN = '*';
|
||||||
const STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet';
|
const STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet';
|
||||||
const STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready';
|
const STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready';
|
||||||
const SOLANA_CHAINS = ['solana:mainnet', 'solana:devnet', 'solana:testnet'];
|
const SOLANA_CHAINS = ['solana:mainnet', 'solana:devnet', 'solana:testnet'];
|
||||||
@ -39,6 +40,45 @@ function createProviderError(message, code = '') {
|
|||||||
return error;
|
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 = {}) {
|
function createRequest(method, params = {}) {
|
||||||
const id = `shine-wallet-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
const id = `shine-wallet-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -59,7 +99,7 @@ function createRequest(method, params = {}) {
|
|||||||
id,
|
id,
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
}, window.location.origin);
|
}, PAGE_MESSAGE_TARGET_ORIGIN);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,12 +162,6 @@ class ShineProviderCore {
|
|||||||
|
|
||||||
async connect(options = {}) {
|
async connect(options = {}) {
|
||||||
const onlyIfTrusted = !!options?.onlyIfTrusted || !!options?.silent;
|
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 result = await createRequest('connect', { onlyIfTrusted });
|
||||||
const nextKey = new PublicKey(String(result?.publicKeyBase58 || '').trim());
|
const nextKey = new PublicKey(String(result?.publicKeyBase58 || '').trim());
|
||||||
this.publicKey = nextKey;
|
this.publicKey = nextKey;
|
||||||
@ -157,10 +191,12 @@ class ShineProviderCore {
|
|||||||
await this.connect();
|
await this.connect();
|
||||||
}
|
}
|
||||||
const transactionBase64 = serializeTransactionBase64(transaction);
|
const transactionBase64 = serializeTransactionBase64(transaction);
|
||||||
|
const transactionSummary = summarizeTransaction(transaction);
|
||||||
const result = await createRequest('signTransaction', {
|
const result = await createRequest('signTransaction', {
|
||||||
publicKeyBase58: this.publicKeyBase58,
|
publicKeyBase58: this.publicKeyBase58,
|
||||||
transactionBase64,
|
transactionBase64,
|
||||||
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
|
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
|
||||||
|
transactionSummary,
|
||||||
});
|
});
|
||||||
return deserializeSignedTransaction(String(result?.signedTransactionBase64 || ''), transaction);
|
return deserializeSignedTransaction(String(result?.signedTransactionBase64 || ''), transaction);
|
||||||
}
|
}
|
||||||
@ -169,10 +205,20 @@ class ShineProviderCore {
|
|||||||
if (!this.publicKey) {
|
if (!this.publicKey) {
|
||||||
await this.connect();
|
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', {
|
const result = await createRequest('signTransaction', {
|
||||||
publicKeyBase58: this.publicKeyBase58,
|
publicKeyBase58: this.publicKeyBase58,
|
||||||
transactionBase64: bytesToBase64(transactionBytes),
|
transactionBase64: bytesToBase64(transactionBytes),
|
||||||
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
|
comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
|
||||||
|
transactionSummary,
|
||||||
});
|
});
|
||||||
return base64ToBytes(String(result?.signedTransactionBase64 || '').trim());
|
return base64ToBytes(String(result?.signedTransactionBase64 || '').trim());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.258
|
client.version=1.2.259
|
||||||
server.version=1.2.243
|
server.version=1.2.244
|
||||||
|
|||||||
@ -240,11 +240,17 @@
|
|||||||
return window.solana;
|
return window.solana;
|
||||||
}
|
}
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
|
const out = document.getElementById("walletInfo");
|
||||||
|
try {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
const r = await provider.connect();
|
const r = await provider.connect();
|
||||||
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
||||||
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
|
out.textContent = "Кошелек: " + walletPubkey.toBase58();
|
||||||
await refreshAll();
|
await refreshAll();
|
||||||
|
} catch (e) {
|
||||||
|
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async function sendInstruction(ix) {
|
async function sendInstruction(ix) {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
|
|||||||
@ -221,11 +221,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
|
const out = document.getElementById("walletInfo");
|
||||||
|
try {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
const r = await provider.connect();
|
const r = await provider.connect();
|
||||||
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
||||||
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
|
out.textContent = "Кошелек: " + walletPubkey.toBase58();
|
||||||
await refreshState();
|
await refreshState();
|
||||||
|
} catch (e) {
|
||||||
|
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendInstruction(ix) {
|
async function sendInstruction(ix) {
|
||||||
|
|||||||
@ -156,11 +156,17 @@
|
|||||||
return window.solana;
|
return window.solana;
|
||||||
}
|
}
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
|
const out = document.getElementById("walletInfo");
|
||||||
|
try {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
const r = await provider.connect();
|
const r = await provider.connect();
|
||||||
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
||||||
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
|
out.textContent = "Кошелек: " + walletPubkey.toBase58();
|
||||||
await refresh();
|
await refresh();
|
||||||
|
} catch (e) {
|
||||||
|
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async function sendInstruction(ix) {
|
async function sendInstruction(ix) {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
|
|||||||
@ -162,11 +162,17 @@
|
|||||||
return window.solana;
|
return window.solana;
|
||||||
}
|
}
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
|
const out = document.getElementById("walletInfo");
|
||||||
|
try {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
const r = await provider.connect();
|
const r = await provider.connect();
|
||||||
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
||||||
document.getElementById("walletInfo").textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
|
out.textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
|
||||||
await refresh();
|
await refresh();
|
||||||
|
} catch (e) {
|
||||||
|
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async function sendInstruction(ix) {
|
async function sendInstruction(ix) {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
|
|||||||
@ -210,11 +210,17 @@
|
|||||||
return window.solana;
|
return window.solana;
|
||||||
}
|
}
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
|
const out = document.getElementById("walletInfo");
|
||||||
|
try {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
const r = await provider.connect();
|
const r = await provider.connect();
|
||||||
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
|
||||||
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
|
out.textContent = "Кошелек: " + walletPubkey.toBase58();
|
||||||
await refreshAll();
|
await refreshAll();
|
||||||
|
} catch (e) {
|
||||||
|
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async function sendInstruction(ix) {
|
async function sendInstruction(ix) {
|
||||||
const provider = getProvider();
|
const provider = getProvider();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user