Синхронизировать изменения проекта

This commit is contained in:
AidarKC
2026-07-05 18:18:03 +04:00
parent 0240db59ee
commit 81bef1e1cc
36 changed files with 573 additions and 335 deletions
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Тех. инструменты — Shine Payments Devnet</title>
<title>Тех. инструменты — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -43,7 +43,7 @@
<body>
<div class="wrap">
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Техническая страница (Devnet)</h1>
<h1>Техническая страница (Mainnet)</h1>
<div class="muted">Программа: <code id="programId"></code></div>
<div class="panel">
@@ -112,7 +112,7 @@
<script>
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
const USERS_PROGRAM_ID = new solanaWeb3.PublicKey("SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6");
const RPC_URL = "https://api.mainnet-beta.solana.com";
const RPC_URL = "https://solana-rpc.publicnode.com";
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
const SEEDS = {
config: "shine_payments_config",
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Покупка билета — Shine Payments Devnet</title>
<title>Покупка билета — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -40,7 +40,7 @@
<body>
<div class="wrap">
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Покупка билета (Devnet)</h1>
<h1>Покупка билета (Mainnet)</h1>
<div class="muted">Программа: <code id="programId"></code></div>
<div class="panel">
@@ -80,7 +80,7 @@
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
<script>
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
const RPC_URL = "https://api.mainnet-beta.solana.com";
const RPC_URL = "https://solana-rpc.publicnode.com";
const ORACLE_ACCOUNT = new solanaWeb3.PublicKey("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DAO revoke vote — Shine Payments Devnet</title>
<title>DAO revoke vote — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -39,7 +39,7 @@
<body>
<div class="wrap">
<div style="margin-bottom: 12px;"><a class="back" href="./index.html">← На главную</a></div>
<h1>DAO: голосование на revoke/burn membership token (Devnet)</h1>
<h1>DAO: голосование на revoke/burn membership token (Mainnet)</h1>
<div class="muted">Governance program: <code id="govPid"></code></div>
<div class="panel">
@@ -97,8 +97,7 @@
import {
Connection,
PublicKey,
Transaction,
clusterApiUrl
Transaction
} from "https://esm.sh/@solana/web3.js@1.95.3";
import {
PROGRAM_VERSION_V3,
@@ -117,7 +116,7 @@
} from "https://esm.sh/@solana/spl-governance@0.3.28";
const GOVERNANCE_PROGRAM_ID = new PublicKey("GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw");
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const connection = new Connection("https://solana-rpc.publicnode.com", "confirmed");
document.getElementById("govPid").textContent = GOVERNANCE_PROGRAM_ID.toBase58();
let wallet = null;
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DAO-права менеджеров — Shine Payments Devnet</title>
<title>DAO-права менеджеров — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -39,7 +39,7 @@
<body>
<div class="wrap">
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>DAO: права менеджеров (Devnet)</h1>
<h1>DAO: права менеджеров (Mainnet)</h1>
<div class="muted">Программа: <code id="programId"></code></div>
<div class="panel">
@@ -85,7 +85,7 @@
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
<script>
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
const RPC_URL = "https://api.mainnet-beta.solana.com";
const RPC_URL = "https://solana-rpc.publicnode.com";
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
const SEEDS = {
config: "shine_payments_config",
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Главная — Shine Payments Devnet</title>
<title>Главная — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -36,7 +36,7 @@
</head>
<body>
<div class="wrap">
<h1>Shine Payments Devnet</h1>
<h1>Shine Payments Mainnet</h1>
<div class="panel">
<div>Выберите страницу:</div>
</div>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Логика работы — Shine Payments Devnet</title>
<title>Логика работы — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -27,7 +27,7 @@
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Логика работы Shine Payments (тестовый этап)</h1>
<div class="panel">
<p>Система работает в <b>Devnet</b>. Экономика хранится в <b>USD-центах</b>, а реальные переводы происходят в SOL.</p>
<p>Система работает в <b>Mainnet</b>. Экономика хранится в <b>USD-центах</b>, а реальные переводы происходят в SOL.</p>
<p>Курс SOL/USD берётся из Pyth прямо в контракте при покупке и при шаге выплаты. Цена проверяется на актуальность (не старше 120 секунд).</p>
</div>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Менеджерские билеты — Shine Payments Devnet</title>
<title>Менеджерские билеты — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -33,13 +33,25 @@
.ok { color: var(--ok); }
.warn { color: var(--warn); }
.err { color: var(--err); white-space: pre-wrap; }
.err-box {
margin-top: 10px;
padding: 10px 12px;
border: 1px solid #4c2730;
border-radius: 8px;
background: #23151a;
color: var(--err);
white-space: pre-wrap;
line-height: 1.45;
font-family: monospace;
font-size: 13px;
}
code { background: var(--code); padding: 2px 4px; border-radius: 4px; }
</style>
</head>
<body>
<div class="wrap">
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Менеджер: создание билетов (Devnet)</h1>
<h1>Менеджер: создание билетов (Mainnet)</h1>
<div class="muted">Программа: <code id="programId"></code></div>
<div class="panel">
@@ -78,7 +90,7 @@
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
<script>
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
const RPC_URL = "https://api.mainnet-beta.solana.com";
const RPC_URL = "https://solana-rpc.publicnode.com";
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
const SEEDS = {
managerAllowance: "shine_p_manager_allow",
@@ -130,6 +142,55 @@
return s.includes("managerlimitexceeded") || s.includes("invalidmanagerwallet");
}
function safeJson(value) {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
function formatSolanaErrorDetails(error) {
const parts = [];
const msg = String(error?.message || error || "").trim();
if (msg) parts.push(msg);
const logs = error?.logs || error?.transactionLogs || error?.simulationLogs || error?.data?.logs;
if (Array.isArray(logs) && logs.length) {
parts.push(`Logs:\n${logs.join("\n")}`);
}
const errObj = error?.value?.err || error?.err || error?.data?.err;
if (errObj) {
parts.push(`Err:\n${safeJson(errObj)}`);
}
if (error?.signature) {
parts.push(`Signature: ${error.signature}`);
}
return parts.length ? parts.join("\n\n") : "unknown";
}
async function attachSolanaLogs(error) {
if (!error) return error;
if (typeof error.getLogs === "function") {
try {
const logs = await error.getLogs(connection);
if (Array.isArray(logs) && logs.length) {
error.logs = logs;
error.transactionLogs = logs;
error.simulationLogs = logs;
}
} catch {
// Игнорируем: часть RPC не умеет отдавать логи для expired/confirm ошибок.
}
}
return error;
}
function parseManagerAllowance(data) {
let o = 0;
const version = data[o++];
@@ -183,8 +244,29 @@
tx.recentBlockhash = bh.blockhash;
const signed = await provider.signTransaction(tx);
const sig = await connection.sendRawTransaction(signed.serialize(), { skipPreflight: false });
await connection.confirmTransaction({ signature: sig, blockhash: bh.blockhash, lastValidBlockHeight: bh.lastValidBlockHeight }, "confirmed");
return sig;
try {
await connection.confirmTransaction(
{ signature: sig, blockhash: bh.blockhash, lastValidBlockHeight: bh.lastValidBlockHeight },
"confirmed",
);
return { signature: sig, confirmed: true };
} catch (confirmError) {
confirmError.signature = sig;
const txInfo = await connection.getTransaction(sig, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
if (txInfo?.meta?.logMessages?.length) {
confirmError.logs = txInfo.meta.logMessages;
confirmError.transactionLogs = txInfo.meta.logMessages;
}
await attachSolanaLogs(confirmError);
const postCheck = await Promise.resolve(typeof ix._postCheck === "function" ? ix._postCheck(sig) : null);
if (postCheck?.success) {
return { signature: sig, confirmed: false, postCheck };
}
throw confirmError;
}
}
function deriveManagerAllowancePda(managerWallet) {
@@ -272,8 +354,17 @@
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
];
const ix = new solanaWeb3.TransactionInstruction({ programId: PROGRAM_ID, keys, data });
const sig = await sendInstruction(ix);
out.innerHTML = `<span class="ok">Билет создан. Tx: <code>${sig}</code></span>`;
ix._postCheck = async () => {
const ai = await connection.getAccountInfo(ticketPda, "confirmed");
if (!ai) return { success: false };
return { success: true, ticketPda: ticketPda.toBase58() };
};
const result = await sendInstruction(ix);
if (result.confirmed) {
out.innerHTML = `<span class="ok">Билет создан. Tx: <code>${result.signature}</code></span>`;
} else {
out.innerHTML = `<span class="warn">Транзакция подтверждалась с задержкой, но билет создан on-chain. Tx: <code>${result.signature}</code></span>`;
}
await refresh();
} catch (e) {
const raw = String(e.message || e);
@@ -281,7 +372,7 @@
out.innerHTML = `<span class="warn">Операция отклонена: лимит менеджера недостаточен или кошелек не имеет прав менеджера.</span>`;
return;
}
out.innerHTML = `<span class="err">${raw}</span>`;
out.innerHTML = `<span class="err">Ошибка создания билета:</span><div class="err-box">${formatSolanaErrorDetails(e)}</div>`;
}
}
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Что ещё нужно до DAO — Shine Payments Devnet</title>
<title>Что ещё нужно до DAO — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -25,7 +25,7 @@
<h1>Что ещё нужно до реального DAO</h1>
<div class="panel">
<p>Сейчас роль DAO выполняет обычный кошелёк (тестовый режим Devnet).</p>
<p>Сейчас роль DAO выполняет обычный кошелёк, а полное on-chain управление через DAO ещё не включено.</p>
</div>
<div class="panel">
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Сценарий тестирования — Shine Payments Devnet</title>
<title>Сценарий тестирования — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -22,7 +22,7 @@
</head>
<body>
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Сценарий тестирования Shine Payments (Devnet)</h1>
<h1>Сценарий тестирования Shine Payments (Mainnet)</h1>
<div class="panel">
<h3>Вариант А: один кошелёк</h3>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Отслеживание билета — Shine Payments Devnet</title>
<title>Отслеживание билета — Shine Payments Mainnet</title>
<style>
:root {
color-scheme: dark;
@@ -42,7 +42,7 @@
<body>
<div class="wrap">
<div class="topbar"><a class="back" href="./index.html">← На главную</a></div>
<h1>Отслеживание билета (Devnet)</h1>
<h1>Отслеживание билета (Mainnet)</h1>
<div class="muted">Программа: <code id="programId"></code></div>
<div class="panel">
@@ -78,7 +78,7 @@
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
<script>
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
const RPC_URL = "https://api.mainnet-beta.solana.com";
const RPC_URL = "https://solana-rpc.publicnode.com";
const ORACLE_ACCOUNT = new solanaWeb3.PublicKey("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");