Исправить подключение и подпись в браузерном кошельке

This commit is contained in:
AidarKC 2026-06-24 10:28:54 +04:00
parent 23e61cc182
commit 684f3237cf
12 changed files with 368 additions and 77 deletions

View File

@ -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,8 +747,15 @@ 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`;
try {
const { response } = await callWalletRpc({
v: 1,
operation: 'sign_transaction',
@ -658,7 +763,7 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
publicKeyBase58: cleanPub,
transactionBase64: cleanTx,
comment: signComment,
}, 120000);
}, 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') {
@ -666,12 +771,16 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64,
}
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();
}
}
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;
}

View File

@ -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;

View File

@ -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;

View File

@ -18,7 +18,6 @@
</div>
<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 class="card-title">Подключение</div>
@ -50,9 +49,6 @@
<div id="session-card" class="card hidden">
<div class="card-title">Подключено</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">
<button id="resume-btn" class="btn secondary" type="button">Проверить session</button>
<button id="disconnect-btn" class="btn danger" type="button">Отключить</button>
@ -72,6 +68,16 @@
</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 class="card-title">Полученный кошелёк</div>
<div class="summary-row"><span>Тип</span><strong id="wallet-type"></strong></div>

View File

@ -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() {

View File

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

View File

@ -1,2 +1,2 @@
client.version=1.2.258
server.version=1.2.243
client.version=1.2.259
server.version=1.2.244

View File

@ -240,11 +240,17 @@
return window.solana;
}
async function connectWallet() {
const out = document.getElementById("walletInfo");
try {
const provider = getProvider();
const r = await provider.connect();
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
out.textContent = "Кошелек: " + walletPubkey.toBase58();
await refreshAll();
} catch (e) {
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
throw e;
}
}
async function sendInstruction(ix) {
const provider = getProvider();

View File

@ -221,11 +221,17 @@
}
async function connectWallet() {
const out = document.getElementById("walletInfo");
try {
const provider = getProvider();
const r = await provider.connect();
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
out.textContent = "Кошелек: " + walletPubkey.toBase58();
await refreshState();
} catch (e) {
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
throw e;
}
}
async function sendInstruction(ix) {

View File

@ -156,11 +156,17 @@
return window.solana;
}
async function connectWallet() {
const out = document.getElementById("walletInfo");
try {
const provider = getProvider();
const r = await provider.connect();
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
out.textContent = "Кошелек: " + walletPubkey.toBase58();
await refresh();
} catch (e) {
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
throw e;
}
}
async function sendInstruction(ix) {
const provider = getProvider();

View File

@ -162,11 +162,17 @@
return window.solana;
}
async function connectWallet() {
const out = document.getElementById("walletInfo");
try {
const provider = getProvider();
const r = await provider.connect();
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
document.getElementById("walletInfo").textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
out.textContent = "Кошелек менеджера: " + walletPubkey.toBase58();
await refresh();
} catch (e) {
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
throw e;
}
}
async function sendInstruction(ix) {
const provider = getProvider();

View File

@ -210,11 +210,17 @@
return window.solana;
}
async function connectWallet() {
const out = document.getElementById("walletInfo");
try {
const provider = getProvider();
const r = await provider.connect();
walletPubkey = new solanaWeb3.PublicKey(r.publicKey.toString());
document.getElementById("walletInfo").textContent = "Кошелек: " + walletPubkey.toBase58();
out.textContent = "Кошелек: " + walletPubkey.toBase58();
await refreshAll();
} catch (e) {
out.innerHTML = `<span class="err">${String(e?.message || e)}</span>`;
throw e;
}
}
async function sendInstruction(ix) {
const provider = getProvider();