SHA256
chore: зафиксированы все текущие изменения проекта
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import { renderHeader } from '../components/header.js';
|
||||
import {
|
||||
SHINE_USERS_ECONOMY_CONFIG_SEED,
|
||||
SHINE_USERS_PROGRAM_ID,
|
||||
SOLANA_CLUSTER,
|
||||
} from '../solana-programs.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export const pageMeta = { id: 'solana-users-init-view', title: 'Solana Init (users)' };
|
||||
|
||||
let solanaLibPromise = null;
|
||||
function loadSolanaLib() {
|
||||
if (!solanaLibPromise) {
|
||||
solanaLibPromise = import('https://esm.sh/@solana/web3.js@1.98.4?bundle');
|
||||
}
|
||||
return solanaLibPromise;
|
||||
}
|
||||
|
||||
function getProvider() {
|
||||
const p = globalThis?.solana;
|
||||
if (!p || !p.isPhantom) return null;
|
||||
return p;
|
||||
}
|
||||
|
||||
function shortAddr(value = '') {
|
||||
const v = String(value || '').trim();
|
||||
if (v.length < 12) return v;
|
||||
return `${v.slice(0, 6)}...${v.slice(-6)}`;
|
||||
}
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card stack';
|
||||
|
||||
const status = document.createElement('p');
|
||||
status.className = 'meta-muted';
|
||||
status.textContent = 'Подключите кошелёк и выполните init_users_economy_config.';
|
||||
|
||||
const programId = SHINE_USERS_PROGRAM_ID;
|
||||
const programInput = document.createElement('input');
|
||||
programInput.className = 'input';
|
||||
programInput.type = 'text';
|
||||
programInput.readOnly = true;
|
||||
programInput.value = programId;
|
||||
|
||||
const rpcInput = document.createElement('input');
|
||||
rpcInput.className = 'input';
|
||||
rpcInput.type = 'text';
|
||||
rpcInput.readOnly = true;
|
||||
rpcInput.value = String(state.entrySettings.solanaServer || '');
|
||||
|
||||
const walletLine = document.createElement('p');
|
||||
walletLine.className = 'meta-muted';
|
||||
walletLine.textContent = 'Кошелёк: не подключен';
|
||||
|
||||
const economyPdaLine = document.createElement('p');
|
||||
economyPdaLine.className = 'meta-muted';
|
||||
economyPdaLine.textContent = 'PDA economy config: —';
|
||||
|
||||
const txLine = document.createElement('p');
|
||||
txLine.className = 'meta-muted';
|
||||
txLine.textContent = 'TX: —';
|
||||
|
||||
const connectBtn = document.createElement('button');
|
||||
connectBtn.className = 'text-btn';
|
||||
connectBtn.type = 'button';
|
||||
connectBtn.textContent = 'Подключить кошелёк';
|
||||
|
||||
const initBtn = document.createElement('button');
|
||||
initBtn.className = 'primary-btn';
|
||||
initBtn.type = 'button';
|
||||
initBtn.textContent = 'Запустить init_users_economy_config';
|
||||
initBtn.disabled = true;
|
||||
|
||||
let provider = null;
|
||||
let walletPubkey = null;
|
||||
let economyPda = null;
|
||||
|
||||
async function recomputePda() {
|
||||
const solana = await loadSolanaLib();
|
||||
const pid = new solana.PublicKey(programId);
|
||||
const [pda] = solana.PublicKey.findProgramAddressSync(
|
||||
[new TextEncoder().encode(SHINE_USERS_ECONOMY_CONFIG_SEED)],
|
||||
pid,
|
||||
);
|
||||
economyPda = pda;
|
||||
economyPdaLine.textContent = `PDA economy config: ${pda.toBase58()}`;
|
||||
}
|
||||
|
||||
connectBtn.addEventListener('click', async () => {
|
||||
status.textContent = 'Подключение кошелька...';
|
||||
try {
|
||||
provider = getProvider();
|
||||
if (!provider) throw new Error('Phantom не найден');
|
||||
const result = await provider.connect();
|
||||
walletPubkey = result?.publicKey || provider.publicKey;
|
||||
if (!walletPubkey) throw new Error('Кошелёк не вернул public key');
|
||||
walletLine.textContent = `Кошелёк: ${walletPubkey.toBase58()} (${shortAddr(walletPubkey.toBase58())})`;
|
||||
initBtn.disabled = false;
|
||||
status.textContent = 'Кошелёк подключен.';
|
||||
} catch (e) {
|
||||
status.textContent = `Ошибка подключения: ${e?.message || 'unknown'}`;
|
||||
}
|
||||
});
|
||||
|
||||
initBtn.addEventListener('click', async () => {
|
||||
if (!provider || !walletPubkey || !economyPda) return;
|
||||
initBtn.disabled = true;
|
||||
status.textContent = 'Отправка транзакции...';
|
||||
txLine.textContent = 'TX: —';
|
||||
try {
|
||||
const solana = await loadSolanaLib();
|
||||
const connection = new solana.Connection(String(state.entrySettings.solanaServer || ''), 'confirmed');
|
||||
const programPubkey = new solana.PublicKey(programId);
|
||||
|
||||
const discriminator = Uint8Array.from([13, 16, 103, 175, 121, 137, 166, 222]);
|
||||
const ix = new solana.TransactionInstruction({
|
||||
programId: programPubkey,
|
||||
keys: [
|
||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||
{ pubkey: economyPda, isSigner: false, isWritable: true },
|
||||
{ pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false },
|
||||
],
|
||||
data: discriminator,
|
||||
});
|
||||
|
||||
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
||||
const tx = new solana.Transaction({
|
||||
feePayer: walletPubkey,
|
||||
blockhash,
|
||||
lastValidBlockHeight,
|
||||
}).add(ix);
|
||||
|
||||
const signed = await provider.signTransaction(tx);
|
||||
const sig = await connection.sendRawTransaction(signed.serialize(), { skipPreflight: false });
|
||||
await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed');
|
||||
|
||||
txLine.textContent = `TX: ${sig}`;
|
||||
status.textContent = 'Успешно: init_users_economy_config выполнен.';
|
||||
} catch (e) {
|
||||
const message = String(e?.message || 'unknown');
|
||||
txLine.textContent = 'TX: ошибка';
|
||||
if (message.toLowerCase().includes('already') || message.includes('1000')) {
|
||||
status.textContent = 'PDA уже инициализирован. Повторный init не требуется.';
|
||||
} else {
|
||||
status.textContent = `Ошибка init: ${message}`;
|
||||
}
|
||||
} finally {
|
||||
initBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
void recomputePda();
|
||||
|
||||
card.append(
|
||||
(() => {
|
||||
const t = document.createElement('p');
|
||||
t.className = 'meta-muted';
|
||||
t.textContent = `Cluster: ${SOLANA_CLUSTER}`;
|
||||
return t;
|
||||
})(),
|
||||
(() => {
|
||||
const label = document.createElement('label');
|
||||
label.className = 'field-label';
|
||||
label.textContent = 'Program ID (shine_users)';
|
||||
return label;
|
||||
})(),
|
||||
programInput,
|
||||
(() => {
|
||||
const label = document.createElement('label');
|
||||
label.className = 'field-label';
|
||||
label.textContent = 'RPC endpoint';
|
||||
return label;
|
||||
})(),
|
||||
rpcInput,
|
||||
walletLine,
|
||||
economyPdaLine,
|
||||
txLine,
|
||||
connectBtn,
|
||||
initBtn,
|
||||
status,
|
||||
);
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Solana Init (users)',
|
||||
leftAction: { label: '←', onClick: () => navigate('developer-settings-view') },
|
||||
}),
|
||||
card,
|
||||
);
|
||||
|
||||
return screen;
|
||||
}
|
||||
Reference in New Issue
Block a user