196 lines
6.4 KiB
JavaScript
196 lines
6.4 KiB
JavaScript
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 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: Uint8Array.from([1]),
|
|
});
|
|
|
|
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;
|
|
}
|