SHA256
Новый протокол Solana PDA 1.2
This commit is contained in:
@@ -544,6 +544,9 @@ export function render({navigate, chrome}) {
|
||||
if (operationBusy) return;
|
||||
|
||||
const normalizedList = uniqueLogins(nextLogins);
|
||||
if (normalizedList.length > 1) {
|
||||
throw new Error('PDA 1.2 поддерживает только один сервер доступа.');
|
||||
}
|
||||
setOperationBusy(true);
|
||||
target.textContent = String(inFlightText || 'Обновляем сервер доступа...');
|
||||
let signingMaterial = null;
|
||||
|
||||
@@ -333,7 +333,7 @@ export function render({ navigate }) {
|
||||
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'meta-muted';
|
||||
subtitle.textContent = 'Процесс запускается сразу: из этого секрета будут вычислены recovery key, root key, blockchain key и client key.';
|
||||
subtitle.textContent = 'Процесс запускается сразу: из этого секрета будут вычислены root/recovery key, blockchain key и client key.';
|
||||
|
||||
const details = document.createElement('p');
|
||||
details.className = 'meta-muted';
|
||||
|
||||
@@ -109,31 +109,17 @@ export function render({ navigate }) {
|
||||
}
|
||||
card.append(makeSecretField({ label: 'Главный секрет (master secret, base58, 32 байта)', value: secretB58 }));
|
||||
|
||||
// Recovery key
|
||||
const recoverySep = document.createElement('p');
|
||||
recoverySep.className = 'field-label';
|
||||
recoverySep.textContent = 'Recovery key';
|
||||
card.append(recoverySep);
|
||||
card.append(makePublicField({
|
||||
label: 'Recovery — публичный (base58)',
|
||||
value: bytesToBase58(base64ToBytes(keyBundle.recoveryPair.publicKeyB64)),
|
||||
}));
|
||||
card.append(makeSecretField({
|
||||
label: 'Recovery — приватный (seed base58, 32 байта)',
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.recoveryPair.privatePkcs8B64)),
|
||||
}));
|
||||
|
||||
// Root key
|
||||
const rootSep = document.createElement('p');
|
||||
rootSep.className = 'field-label';
|
||||
rootSep.textContent = 'Root key';
|
||||
rootSep.textContent = 'Root key (recovery)';
|
||||
card.append(rootSep);
|
||||
card.append(makePublicField({
|
||||
label: 'Root — публичный (base58)',
|
||||
label: 'Root / recovery — публичный (base58)',
|
||||
value: bytesToBase58(base64ToBytes(keyBundle.rootPair.publicKeyB64)),
|
||||
}));
|
||||
card.append(makeSecretField({
|
||||
label: 'Root — приватный (seed base58, 32 байта)',
|
||||
label: 'Root / recovery — приватный (seed base58, 32 байта)',
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.rootPair.privatePkcs8B64)),
|
||||
}));
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const REGISTRATION_FAQ_TOPICS = [
|
||||
title: 'Как генерируются ключи и что делает пароль?',
|
||||
paragraphs: [
|
||||
'Из вашего логина и пароля с помощью Argon2id вычисляется специальный секрет.',
|
||||
'Уже из этого секрета детерминированно строятся четыре основных ключа: recovery key, root key, blockchain key и client key.',
|
||||
'Уже из этого секрета детерминированно строятся три основных ключа: root/recovery key, blockchain key и client key.',
|
||||
'Это значит, что логин и пароль не просто проверяются на сервере, а реально участвуют в создании ваших ключей. У разных логинов даже с одинаковым паролем будут разные ключи.',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1133,11 +1133,7 @@ export class AuthService {
|
||||
});
|
||||
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
|
||||
|
||||
if (onProgress) onProgress({ percent: 93, stage: 'derive', message: 'Вычисление recovery key...' });
|
||||
const recoveryPair = await deriveEd25519FromMasterSecret(masterSecret, 'recovery.key');
|
||||
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
|
||||
|
||||
if (onProgress) onProgress({ percent: 95, stage: 'derive', message: 'Вычисление root key...' });
|
||||
if (onProgress) onProgress({ percent: 94, stage: 'derive', message: 'Вычисление root key...' });
|
||||
const rootPair = await deriveEd25519FromMasterSecret(masterSecret, 'root.key');
|
||||
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
|
||||
|
||||
@@ -1149,7 +1145,6 @@ export class AuthService {
|
||||
const clientPair = await deriveEd25519FromMasterSecret(masterSecret, 'client.key');
|
||||
const result = {
|
||||
masterSecretB64: bytesToBase64(masterSecret),
|
||||
recoveryPair,
|
||||
rootPair,
|
||||
blockchainPair,
|
||||
clientPair,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
|
||||
<h1>Регистрация серверного аккаунта</h1>
|
||||
<p class="subtitle">Создаёт user_pda в Solana с флагом is_server=true</p>
|
||||
<p class="subtitle">Создаёт серверную user_pda в Solana (PDA 1.2)</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Параметры Solana</h2>
|
||||
@@ -65,12 +65,8 @@
|
||||
<input type="text" id="serverAddress" placeholder="Адрес сервера" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Логины серверов c которыми будет синхронизировать записи в блокчейн этот сервер (Например shineupme)</label>
|
||||
<textarea id="syncServers" placeholder="По одному логину на строку (можно оставить пустым)"></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Серверы доступа (access_servers, опционально)</label>
|
||||
<textarea id="accessServers" placeholder="Обычно пусто для серверного PDA"></textarea>
|
||||
<label>Сервер доступа (access_servers, 0 или 1)</label>
|
||||
<textarea id="accessServers" placeholder="Один логин сервера доступа или пусто"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,17 +95,12 @@
|
||||
<div class="sec-lbl">Ключевые пары (base58)</div>
|
||||
|
||||
<div class="kp-block">
|
||||
<div class="kp-title">Recovery Key — восстановление аккаунта</div>
|
||||
<div class="kp-row"><span class="kp-lbl">Публичный</span><input class="kp-inp" type="text" id="recoveryPub" placeholder="base58, ~44 символа" /></div>
|
||||
<div class="kp-row"><span class="kp-lbl">Приватный</span><input class="kp-inp" type="text" id="recoveryPriv" placeholder="seed base58, ~44 символа" /></div>
|
||||
</div>
|
||||
<div class="kp-block">
|
||||
<div class="kp-title">Root Key — подпись PDA-записи</div>
|
||||
<div class="kp-title">Root Key — холодное восстановление / полный контроль PDA</div>
|
||||
<div class="kp-row"><span class="kp-lbl">Публичный</span><input class="kp-inp" type="text" id="rootPub" placeholder="base58, ~44 символа" /></div>
|
||||
<div class="kp-row"><span class="kp-lbl">Приватный</span><input class="kp-inp" type="text" id="rootPriv" placeholder="seed base58, ~44 символа" /></div>
|
||||
</div>
|
||||
<div class="kp-block">
|
||||
<div class="kp-title">Blockchain Key — подпись LastBlockState</div>
|
||||
<div class="kp-title">Blockchain Key — активный ключ блокчейна и обычный authority PDA</div>
|
||||
<div class="kp-row"><span class="kp-lbl">Публичный</span><input class="kp-inp" type="text" id="bchPub" placeholder="base58, ~44 символа" /></div>
|
||||
<div class="kp-row"><span class="kp-lbl">Приватный</span><input class="kp-inp" type="text" id="bchPriv" placeholder="seed base58, ~44 символа" /></div>
|
||||
</div>
|
||||
|
||||
@@ -20,8 +20,6 @@ import { defaultServerAddress, defaultServerLogin, defaultSolanaEndpoint } from
|
||||
|
||||
const fieldMap = {
|
||||
masterSecret: 'masterSecret',
|
||||
recoveryPub: 'recoveryPub',
|
||||
recoveryPriv: 'recoveryPriv',
|
||||
rootPub: 'rootPub',
|
||||
rootPriv: 'rootPriv',
|
||||
bchPub: 'bchPub',
|
||||
@@ -113,7 +111,6 @@ $('btnCreate').addEventListener('click', async () => {
|
||||
login,
|
||||
keyBundle,
|
||||
serverAddress,
|
||||
syncServers: parseLoginList($('syncServers').value),
|
||||
accessServers: parseLoginList($('accessServers').value),
|
||||
solanaEndpoint: endpoint,
|
||||
});
|
||||
|
||||
@@ -112,6 +112,7 @@ function renderParsed(parsed) {
|
||||
|
||||
renderRows([
|
||||
{ label: 'PDA адрес', value: parsed.pdaAddress },
|
||||
{ label: 'Формат', value: `${parsed.formatMajor}.${parsed.formatMinor}` },
|
||||
{ label: 'Логин', value: parsed.login },
|
||||
{ label: 'Статус', value: parsed.isServer ? 'server' : 'not server' },
|
||||
{ label: 'recordNumber', value: String(parsed.recordNumber) },
|
||||
@@ -126,11 +127,10 @@ function renderParsed(parsed) {
|
||||
const badgeLine = $('badgeLine');
|
||||
badgeLine.innerHTML = '';
|
||||
const badges = [
|
||||
{ label: 'format=1.2', kind: 'ok' },
|
||||
{ label: `server=${parsed.isServer ? '1' : '0'}`, kind: parsed.isServer ? 'ok' : 'warn' },
|
||||
{ label: `trusted=${Number(parsed.trustedCount || 0)}`, kind: Number(parsed.trustedCount || 0) > 0 ? 'ok' : 'warn' },
|
||||
{ label: `sessions=${Array.isArray(parsed.sessions) ? parsed.sessions.length : 0}`, kind: 'info' },
|
||||
{ label: `forks=${Array.isArray(parsed.forks) ? parsed.forks.length : 0}`, kind: 'info' },
|
||||
{ label: `access=${Array.isArray(parsed.accessServers) ? parsed.accessServers.length : 0}`, kind: 'info' },
|
||||
{ label: `sync=${Array.isArray(parsed.syncServers) ? parsed.syncServers.length : 0}`, kind: 'info' },
|
||||
];
|
||||
badges.forEach((item) => {
|
||||
const badge = document.createElement('span');
|
||||
@@ -141,84 +141,55 @@ function renderParsed(parsed) {
|
||||
|
||||
const blocks = $('blocksList');
|
||||
blocks.innerHTML = '';
|
||||
blocks.appendChild(renderBlock('RecoveryKeyBlock', 'block_type=0', [
|
||||
{ label: 'recoveryKey32', value: base58(parsed.recoveryKey) },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('RootKeyBlock', 'block_type=1', [
|
||||
blocks.appendChild(renderBlock('RootKeyBlock', 'block_type=1, version=0', [
|
||||
{ label: 'rootKey32', value: base58(parsed.rootKey) },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('ClientKeyBlock', 'block_type=2', [
|
||||
blocks.appendChild(renderBlock('ClientKeyBlock', 'block_type=2, version=0', [
|
||||
{ label: 'clientKey32', value: base58(parsed.clientKey) },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('BlockchainRegistryBlock', 'block_type=3', [
|
||||
{ label: 'blockchainType', value: String(parsed.blockchain?.blockchainType ?? 0) },
|
||||
{ label: 'blockchainName', value: parsed.blockchain?.blockchainName || '—' },
|
||||
{ label: 'blockchainPublicKey32', value: base58(parsed.blockchain?.blockchainPublicKey) },
|
||||
{ label: 'paidLimitBytes', value: formatBigInt(parsed.blockchain?.paidLimitBytes || 0n) },
|
||||
{ label: 'usedBytes', value: formatBigInt(parsed.blockchain?.usedBytes || 0n) },
|
||||
{ label: 'lastBlockNumber', value: String(parsed.blockchain?.lastBlockNumber ?? 0) },
|
||||
{ label: 'lastBlockHash32', value: hex(parsed.blockchain?.lastBlockHash) },
|
||||
{ label: 'lastBlockSignature64', value: base58(parsed.blockchain?.lastBlockSignature) },
|
||||
{ label: 'arweaveTxId', value: parsed.blockchain?.arweaveTxId || '—' },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('ServerProfileBlock', 'block_type=30', [
|
||||
{ label: 'isServer', value: parsed.isServer ? '1' : '0' },
|
||||
{ label: 'addressFormatType', value: String(parsed.addressFormatType ?? 0) },
|
||||
{ label: 'addressFormatVersion', value: String(parsed.addressFormatVersion ?? 0) },
|
||||
{ label: 'serverAddress', value: parsed.serverAddress || '—' },
|
||||
{ label: 'syncServersCount', value: String(parsed.syncServers?.length || 0) },
|
||||
{ label: 'syncServers', value: renderArrayLine(parsed.syncServers) },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('AccessServersBlock', 'block_type=40', [
|
||||
{ label: 'accessServersCount', value: String(parsed.accessServers?.length || 0) },
|
||||
{ label: 'accessServers', value: renderArrayLine(parsed.accessServers) },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('SessionsBlock', 'block_type=50', [
|
||||
{ label: 'sessionsMode', value: String(parsed.sessionsMode ?? 0) },
|
||||
{ label: 'sessionsCount', value: String(parsed.sessions?.length || 0) },
|
||||
{ label: 'sessions', value: parsed.sessions?.length ? 'ниже' : '—' },
|
||||
]));
|
||||
blocks.appendChild(renderBlock('TrustedStateBlock', 'block_type=70', [
|
||||
{ label: 'trustedCount', value: String(parsed.trustedCount ?? 0) },
|
||||
]));
|
||||
blocks.appendChild(renderSessionList(parsed.sessions));
|
||||
|
||||
$('rawJson').textContent = JSON.stringify({
|
||||
const forkLines = (parsed.forks || []).map((fork, idx) =>
|
||||
`#${idx}: ${base58(fork.blockchainKey)} · created=${formatTimestamp(fork.createdAtMs)} · limit=${formatBigInt(fork.paidLimitBytes)}`,
|
||||
);
|
||||
blocks.appendChild(renderBlock('BlockchainRegistryBlock', 'block_type=3, version=0', [
|
||||
{ label: 'forkCount', value: String(parsed.forks?.length || 0) },
|
||||
{ label: 'forks', value: forkLines.length ? forkLines.join('\n') : '—' },
|
||||
]));
|
||||
|
||||
if (parsed.isServer) {
|
||||
const addressLines = (parsed.serverAddresses || []).map((item) =>
|
||||
`type=${item.addressFormatType}, v=${item.addressFormatVersion}: ${item.address}`,
|
||||
);
|
||||
blocks.appendChild(renderBlock('ServerProfileBlock', 'block_type=30, version=0', [
|
||||
{ label: 'addressCount', value: String(parsed.serverAddresses?.length || 0) },
|
||||
{ label: 'address', value: addressLines.length ? addressLines.join('\n') : '—' },
|
||||
]));
|
||||
}
|
||||
if (Array.isArray(parsed.accessServers) && parsed.accessServers.length) {
|
||||
blocks.appendChild(renderBlock('AccessServersBlock', 'block_type=40, version=0', [
|
||||
{ label: 'accessServersCount', value: String(parsed.accessServers.length) },
|
||||
{ label: 'accessServer', value: renderArrayLine(parsed.accessServers) },
|
||||
]));
|
||||
}
|
||||
|
||||
const raw = {
|
||||
pdaAddress: parsed.pdaAddress,
|
||||
format: `${parsed.formatMajor}.${parsed.formatMinor}`,
|
||||
login: parsed.login,
|
||||
isServer: parsed.isServer,
|
||||
recordNumber: parsed.recordNumber,
|
||||
createdAtMs: parsed.createdAtMs.toString(),
|
||||
updatedAtMs: parsed.updatedAtMs.toString(),
|
||||
recordLen: parsed.recordLen,
|
||||
trustedCount: parsed.trustedCount,
|
||||
addressFormatType: parsed.addressFormatType,
|
||||
addressFormatVersion: parsed.addressFormatVersion,
|
||||
serverAddress: parsed.serverAddress,
|
||||
syncServers: parsed.syncServers,
|
||||
accessServers: parsed.accessServers,
|
||||
sessionsMode: parsed.sessionsMode,
|
||||
sessions: parsed.sessions.map((s) => ({
|
||||
sessionType: s.sessionType,
|
||||
sessionVersion: s.sessionVersion,
|
||||
sessionName: s.sessionName,
|
||||
sessionPubKey32: base58(s.sessionPubKey32),
|
||||
})),
|
||||
recoveryKey: base58(parsed.recoveryKey),
|
||||
prevRecordHash: hex(parsed.prevRecordHash),
|
||||
rootKey: base58(parsed.rootKey),
|
||||
clientKey: base58(parsed.clientKey),
|
||||
blockchain: {
|
||||
blockchainType: parsed.blockchain.blockchainType,
|
||||
blockchainName: parsed.blockchain.blockchainName,
|
||||
blockchainPublicKey: base58(parsed.blockchain.blockchainPublicKey),
|
||||
paidLimitBytes: parsed.blockchain.paidLimitBytes.toString(),
|
||||
usedBytes: parsed.blockchain.usedBytes.toString(),
|
||||
lastBlockNumber: parsed.blockchain.lastBlockNumber,
|
||||
lastBlockHash: hex(parsed.blockchain.lastBlockHash),
|
||||
lastBlockSignature: base58(parsed.blockchain.lastBlockSignature),
|
||||
arweaveTxId: parsed.blockchain.arweaveTxId || '',
|
||||
},
|
||||
}, null, 2);
|
||||
serverAddresses: (parsed.serverAddresses || []).map((a) => ({ ...a })),
|
||||
accessServers: parsed.accessServers || [],
|
||||
signature: base58(parsed.signature),
|
||||
};
|
||||
|
||||
$('rawJson').textContent = JSON.stringify(raw, null, 2);
|
||||
}
|
||||
|
||||
$('btnLoad').addEventListener('click', async () => {
|
||||
|
||||
@@ -111,18 +111,6 @@ async function pairFromSeedBase58(seedB58, explicitPubB58) {
|
||||
|
||||
export async function buildKeyBundleFromForm(fieldMap, options = {}) {
|
||||
const requireBlockchain = options.requireBlockchain !== false;
|
||||
let recovery = null;
|
||||
const masterSecretValue = fieldMap.masterSecret ? String($(fieldMap.masterSecret).value || '').trim() : '';
|
||||
if (masterSecretValue) {
|
||||
const masterSecret32 = ensure32Bytes(base58ToBytes(masterSecretValue));
|
||||
const recoveryPair = await deriveEd25519FromMasterSecret(masterSecret32, 'recovery.key');
|
||||
recovery = {
|
||||
publicKeyB64: recoveryPair.publicKeyB64,
|
||||
privatePkcs8B64: recoveryPair.privatePkcs8B64,
|
||||
publicKeyB58: bytesToBase58(base64ToBytes(recoveryPair.publicKeyB64)),
|
||||
privateSeedB58: bytesToBase58(base64ToBytes(recoveryPair.privatePkcs8B64).slice(-32)),
|
||||
};
|
||||
}
|
||||
const root = await pairFromSeedBase58($(fieldMap.rootPriv).value, $(fieldMap.rootPub).value);
|
||||
const device = await pairFromSeedBase58($(fieldMap.devPriv).value, $(fieldMap.devPub).value);
|
||||
const blockchainPriv = String($(fieldMap.bchPriv).value || '').trim();
|
||||
@@ -134,9 +122,6 @@ export async function buildKeyBundleFromForm(fieldMap, options = {}) {
|
||||
}
|
||||
return {
|
||||
keyBundle: {
|
||||
recoveryPair: recovery
|
||||
? { publicKeyB64: recovery.publicKeyB64, privatePkcs8B64: recovery.privatePkcs8B64 }
|
||||
: null,
|
||||
rootPair: { publicKeyB64: root.publicKeyB64, privatePkcs8B64: root.privatePkcs8B64 },
|
||||
blockchainPair: blockchain
|
||||
? { publicKeyB64: blockchain.publicKeyB64, privatePkcs8B64: blockchain.privatePkcs8B64 }
|
||||
@@ -144,8 +129,6 @@ export async function buildKeyBundleFromForm(fieldMap, options = {}) {
|
||||
clientPair: { publicKeyB64: device.publicKeyB64, privatePkcs8B64: device.privatePkcs8B64 },
|
||||
},
|
||||
normalized: {
|
||||
recoveryPubB58: recovery?.publicKeyB58 || '',
|
||||
recoveryPrivB58: recovery?.privateSeedB58 || '',
|
||||
rootPubB58: root.publicKeyB58,
|
||||
rootPrivB58: root.privateSeedB58,
|
||||
bchPubB58: blockchain?.publicKeyB58 || '',
|
||||
@@ -165,20 +148,14 @@ export async function deriveKeyBundleFromPassword({ login, password, onProgress
|
||||
login: cleanLogin,
|
||||
onProgress,
|
||||
});
|
||||
const [recoveryPair, rootPair, blockchainPair, clientPair] = await Promise.all([
|
||||
deriveEd25519FromMasterSecret(masterSecret32, 'recovery.key'),
|
||||
const [rootPair, blockchainPair, clientPair] = await Promise.all([
|
||||
deriveEd25519FromMasterSecret(masterSecret32, 'root.key'),
|
||||
deriveEd25519FromMasterSecret(masterSecret32, 'blockchain.key'),
|
||||
deriveEd25519FromMasterSecret(masterSecret32, 'client.key'),
|
||||
]);
|
||||
return {
|
||||
masterSecret32,
|
||||
keyBundle: {
|
||||
recoveryPair,
|
||||
rootPair,
|
||||
blockchainPair,
|
||||
clientPair,
|
||||
},
|
||||
keyBundle: { rootPair, blockchainPair, clientPair },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -186,10 +163,6 @@ export function fillKeyFields(fieldMap, keyBundle, masterSecret32) {
|
||||
if (masterSecret32) {
|
||||
$(fieldMap.masterSecret).value = bytesToBase58(masterSecret32);
|
||||
}
|
||||
if (fieldMap.recoveryPub && fieldMap.recoveryPriv && keyBundle.recoveryPair) {
|
||||
$(fieldMap.recoveryPub).value = bytesToBase58(base64ToBytes(keyBundle.recoveryPair.publicKeyB64));
|
||||
$(fieldMap.recoveryPriv).value = bytesToBase58(base64ToBytes(keyBundle.recoveryPair.privatePkcs8B64).slice(-32));
|
||||
}
|
||||
$(fieldMap.rootPub).value = bytesToBase58(base64ToBytes(keyBundle.rootPair.publicKeyB64));
|
||||
$(fieldMap.rootPriv).value = bytesToBase58(base64ToBytes(keyBundle.rootPair.privatePkcs8B64).slice(-32));
|
||||
$(fieldMap.bchPub).value = bytesToBase58(base64ToBytes(keyBundle.blockchainPair.publicKeyB64));
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
formatBigInt,
|
||||
formatTimestamp,
|
||||
openDevnetTopup,
|
||||
parseLoginList,
|
||||
publicKeyBytesToBase58,
|
||||
refreshDeviceBalance,
|
||||
setGenMessage,
|
||||
@@ -26,8 +25,6 @@ import { defaultServerAddress, defaultServerLogin, defaultSolanaEndpoint } from
|
||||
|
||||
const fieldMap = {
|
||||
masterSecret: 'masterSecret',
|
||||
recoveryPub: 'recoveryPub',
|
||||
recoveryPriv: 'recoveryPriv',
|
||||
rootPub: 'rootPub',
|
||||
rootPriv: 'rootPriv',
|
||||
bchPub: 'bchPub',
|
||||
@@ -42,7 +39,6 @@ let currentPda = null;
|
||||
|
||||
function resetExpectedKeysUi() {
|
||||
$('expectedKeysBox').style.display = 'none';
|
||||
setText('expectedRecoveryPub', '');
|
||||
setText('expectedRootPub', '');
|
||||
setText('expectedBchPub', '');
|
||||
setText('expectedDevPub', '');
|
||||
@@ -52,7 +48,6 @@ function resetExpectedKeysUi() {
|
||||
|
||||
function renderExpectedKeys(parsed) {
|
||||
$('expectedKeysBox').style.display = 'block';
|
||||
setText('expectedRecoveryPub', publicKeyBytesToBase58(parsed.recoveryKey));
|
||||
setText('expectedRootPub', publicKeyBytesToBase58(parsed.rootKey));
|
||||
setText('expectedBchPub', publicKeyBytesToBase58(parsed.blockchain.blockchainPublicKey));
|
||||
setText('expectedDevPub', publicKeyBytesToBase58(parsed.clientKey));
|
||||
@@ -64,7 +59,6 @@ function compareCurrentFormKeysWithPda() {
|
||||
const blockchainActual = String($('bchPub').value || '').trim();
|
||||
return {
|
||||
resultMap: {
|
||||
recovery: compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.recoveryKey), $('recoveryPub').value),
|
||||
root: compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.rootKey), $('rootPub').value),
|
||||
blockchain: blockchainActual
|
||||
? compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.blockchain.blockchainPublicKey), blockchainActual)
|
||||
@@ -178,17 +172,15 @@ $('btnLoad').addEventListener('click', async () => {
|
||||
$('iCreated').textContent = formatTimestamp(parsed.createdAtMs);
|
||||
$('iUpdated').textContent = formatTimestamp(parsed.updatedAtMs);
|
||||
$('iSrvAddr').textContent = parsed.serverAddress || '—';
|
||||
$('iSync').textContent = parsed.syncServers.length ? parsed.syncServers.join(', ') : '—';
|
||||
$('iBch').textContent = parsed.blockchain.blockchainName;
|
||||
$('iLimit').textContent = formatBigInt(parsed.blockchain.paidLimitBytes);
|
||||
|
||||
$('password').value = '';
|
||||
$('serverAddress').value = parsed.serverAddress || '';
|
||||
$('syncServers').value = parsed.syncServers.join('\n');
|
||||
$('pdaInfo').style.display = 'block';
|
||||
$('updateForm').style.display = 'block';
|
||||
renderExpectedKeys(parsed);
|
||||
setStatus($('status'), 'PDA загружена. Можно менять адрес или sync_servers.', 'success');
|
||||
setStatus($('status'), 'PDA загружена. Можно менять адрес сервера.', 'success');
|
||||
} catch (error) {
|
||||
setStatus($('status'), error?.message || String(error), 'error');
|
||||
} finally {
|
||||
@@ -227,7 +219,6 @@ $('btnUpdate').addEventListener('click', async () => {
|
||||
serverAddress,
|
||||
addressFormatType: currentPda.addressFormatType ?? 1,
|
||||
addressFormatVersion: currentPda.addressFormatVersion ?? 0,
|
||||
syncServers: parseLoginList($('syncServers').value),
|
||||
solanaEndpoint: endpoint,
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<h1>Обновление PDA сервера</h1>
|
||||
<p class="subtitle">Меняет адрес сервера или список серверов синхронизации</p>
|
||||
<p class="subtitle">Меняет адрес SHiNE-сервера и другие поля PDA 1.2</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Параметры Solana</h2>
|
||||
@@ -76,7 +76,6 @@
|
||||
<div class="pda-row"><span class="pda-key">Создан</span><span class="pda-value" id="iCreated"></span></div>
|
||||
<div class="pda-row"><span class="pda-key">Обновлён</span><span class="pda-value" id="iUpdated"></span></div>
|
||||
<div class="pda-row"><span class="pda-key">Адрес сервера</span><span class="pda-value" id="iSrvAddr"></span></div>
|
||||
<div class="pda-row"><span class="pda-key">sync_servers</span><span class="pda-value" id="iSync"></span></div>
|
||||
<div class="pda-row"><span class="pda-key">Blockchain</span><span class="pda-value" id="iBch"></span></div>
|
||||
<div class="pda-row"><span class="pda-key">Paid limit</span><span class="pda-value" id="iLimit"></span></div>
|
||||
</div>
|
||||
@@ -89,10 +88,6 @@
|
||||
<label>Арес сервера (URL) например shineup.me</label>
|
||||
<input type="text" id="serverAddress" placeholder="Адрес сервера" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Логины серверов c которыми будет синхронизировать записи в блокчейн этот сервер (Например shineupme)</label>
|
||||
<textarea id="syncServers" placeholder="По одному логину на строку (можно оставить пустым)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@@ -120,12 +115,7 @@
|
||||
<div class="sec-lbl">Ключевые пары (base58)</div>
|
||||
|
||||
<div class="kp-block">
|
||||
<div class="kp-title">Recovery Key — восстановление аккаунта</div>
|
||||
<div class="kp-row"><span class="kp-lbl">Публичный</span><input class="kp-inp" type="text" id="recoveryPub" placeholder="base58, ~44 символа" /></div>
|
||||
<div class="kp-row"><span class="kp-lbl">Приватный</span><input class="kp-inp" type="text" id="recoveryPriv" placeholder="seed base58, ~44 символа" /></div>
|
||||
</div>
|
||||
<div class="kp-block">
|
||||
<div class="kp-title">Root Key — подпись PDA-записи</div>
|
||||
<div class="kp-title">Root Key — холодное восстановление / полный контроль PDA</div>
|
||||
<div class="kp-row"><span class="kp-lbl">Публичный</span><input class="kp-inp" type="text" id="rootPub" placeholder="base58, ~44 символа" /></div>
|
||||
<div class="kp-row"><span class="kp-lbl">Приватный</span><input class="kp-inp" type="text" id="rootPriv" placeholder="seed base58, ~44 символа" /></div>
|
||||
</div>
|
||||
@@ -150,10 +140,6 @@
|
||||
|
||||
<div class="expected-card" id="expectedKeysBox" style="display:none">
|
||||
<div class="expected-ttl">Какие ключи ожидаются по уже загруженной PDA</div>
|
||||
<div class="expected-row">
|
||||
<div class="expected-lbl">Ожидаемый recovery public key</div>
|
||||
<div class="expected-val" id="expectedRecoveryPub"></div>
|
||||
</div>
|
||||
<div class="expected-row">
|
||||
<div class="expected-lbl">Ожидаемый root public key</div>
|
||||
<div class="expected-val" id="expectedRootPub"></div>
|
||||
|
||||
@@ -1032,8 +1032,8 @@ select:focus {
|
||||
/* Viewer v3 */
|
||||
:root{--v3-bg:#f5f5f3;--v3-panel:#fff;--v3-text:#171717;--v3-sub:#747474;--v3-border:#e7e7e3;--v3-hover:#f0f0ed;--v3-shadow:0 18px 60px rgba(0,0,0,.08)}
|
||||
html[data-theme="dark"]{color-scheme:dark;--v3-bg:#101110;--v3-panel:#181918;--v3-text:#f3f3ef;--v3-sub:#9c9d98;--v3-border:#292a28;--v3-hover:#222320;--v3-shadow:0 18px 60px rgba(0,0,0,.28)}
|
||||
html,body{background:var(--v3-bg)!important;color:var(--v3-text)!important}.viewer-app{min-height:100vh;background:var(--v3-bg);padding-bottom:54px}.viewer-toolbar{position:sticky;top:0;z-index:30;height:64px;padding:0 max(18px,calc((100vw - 980px)/2));display:flex;align-items:center;gap:10px;background:color-mix(in srgb,var(--v3-bg) 88%,transparent);backdrop-filter:blur(18px);border-bottom:1px solid var(--v3-border);overflow:visible!important}
|
||||
.v3-brand{font-weight:800;letter-spacing:-.03em;margin-right:auto}.v3-tabs{display:flex;padding:4px;background:var(--v3-hover);border-radius:12px}.v3-tab{border:0;background:transparent;color:var(--v3-sub);padding:8px 13px;border-radius:9px;font-weight:700;cursor:pointer}.v3-tab.is-active{background:var(--v3-panel);color:var(--v3-text);box-shadow:0 1px 5px rgba(0,0,0,.08)}.toolbar-icon{background:var(--v3-panel)!important;color:var(--v3-text)!important;border:1px solid var(--v3-border)!important;border-radius:11px!important;width:40px;height:40px}.v3-menu{position:absolute;right:max(18px,calc((100vw - 980px)/2));top:58px;width:min(340px,calc(100vw - 28px));padding:14px;background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:16px;box-shadow:var(--v3-shadow);z-index:1000}.v3-menu label{color:var(--v3-text)}.v3-menu input{background:var(--v3-bg);color:var(--v3-text);border-color:var(--v3-border)}.v3-switch{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 2px 14px}.viewer-main{width:min(860px,calc(100% - 28px));margin:22px auto!important}.mobile-card,.message-bubble,.channel-row{background:var(--v3-panel)!important;color:var(--v3-text)!important;border-color:var(--v3-border)!important;box-shadow:none!important}.message-bubble__meta,.channel-row__meta,.channel-row__preview,.channel-head p{color:var(--v3-sub)!important}.thread-toggle{border:0;background:transparent;color:var(--v3-sub);padding:8px 0 2px;font-weight:700;cursor:pointer}.thread-toggle:hover{color:var(--v3-text)}.message-thread__replies{border-left:1px solid var(--v3-border)!important;margin-left:18px!important;padding-left:16px!important}.v3-status{position:fixed;z-index:40;left:0;right:0;bottom:0;height:38px;display:flex;align-items:center;padding:0 max(18px,calc((100vw - 980px)/2));background:var(--v3-panel);border-top:1px solid var(--v3-border);font-size:12px;color:var(--v3-sub);cursor:pointer}.v3-status-detail{position:fixed;z-index:39;left:50%;bottom:38px;transform:translateX(-50%);width:min(760px,calc(100% - 28px));max-height:45vh;overflow:auto;background:var(--v3-panel);border:1px solid var(--v3-border);border-bottom:0;border-radius:16px 16px 0 0;padding:16px;box-shadow:var(--v3-shadow);font:12px/1.55 var(--mono);white-space:pre-wrap}.v3-start{max-width:520px;margin:12vh auto;padding:28px;background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:24px;box-shadow:var(--v3-shadow)}.v3-start h1{margin:0 0 8px;font-size:32px;letter-spacing:-.04em}.v3-start p{color:var(--v3-sub);line-height:1.5}.v3-start-grid{display:grid;gap:12px}.v3-start input{background:var(--v3-bg);color:var(--v3-text);border-color:var(--v3-border)}.v3-open{height:48px;border:0;border-radius:12px;background:var(--v3-text);color:var(--v3-bg);font-weight:800;cursor:pointer}.v3-blocks{display:grid;gap:8px}.v3-block{background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:14px;padding:12px 14px}.v3-block summary{cursor:pointer;font-weight:750}.v3-block pre{overflow:auto;color:var(--v3-sub);white-space:pre-wrap}.message-inline-media{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:10px}.message-inline-media:has(>a:only-child){grid-template-columns:minmax(0,1fr)}.message-inline-media a{display:block;overflow:hidden;border-radius:12px;border:1px solid var(--v3-border);background:var(--v3-bg)}.message-inline-media img{display:block;width:100%;max-height:520px;object-fit:contain;background:var(--v3-bg)}@media(max-width:520px){.message-inline-media{grid-template-columns:1fr}.message-inline-media img{max-height:70vh}}
|
||||
html,body{background:var(--v3-bg)!important;color:var(--v3-text)!important}.viewer-app{min-height:100vh;background:var(--v3-bg);padding-bottom:54px}.viewer-toolbar{position:sticky;top:0;z-index:30;height:64px;padding:0 max(18px,calc((100vw - 980px)/2));display:flex;align-items:center;gap:10px;background:color-mix(in srgb,var(--v3-bg) 88%,transparent);backdrop-filter:blur(18px);border-bottom:1px solid var(--v3-border)}
|
||||
.v3-brand{font-weight:800;letter-spacing:-.03em;margin-right:auto}.v3-tabs{display:flex;padding:4px;background:var(--v3-hover);border-radius:12px}.v3-tab{border:0;background:transparent;color:var(--v3-sub);padding:8px 13px;border-radius:9px;font-weight:700;cursor:pointer}.v3-tab.is-active{background:var(--v3-panel);color:var(--v3-text);box-shadow:0 1px 5px rgba(0,0,0,.08)}.toolbar-icon{background:var(--v3-panel)!important;color:var(--v3-text)!important;border:1px solid var(--v3-border)!important;border-radius:11px!important;width:40px;height:40px}.v3-menu{position:absolute;right:max(18px,calc((100vw - 980px)/2));top:58px;width:min(340px,calc(100vw - 28px));padding:14px;background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:16px;box-shadow:var(--v3-shadow);z-index:50}.v3-menu label{color:var(--v3-text)}.v3-menu input{background:var(--v3-bg);color:var(--v3-text);border-color:var(--v3-border)}.v3-switch{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 2px 14px}.viewer-main{width:min(860px,calc(100% - 28px));margin:22px auto!important}.mobile-card,.message-bubble,.channel-row{background:var(--v3-panel)!important;color:var(--v3-text)!important;border-color:var(--v3-border)!important;box-shadow:none!important}.message-bubble__meta,.channel-row__meta,.channel-row__preview,.channel-head p{color:var(--v3-sub)!important}.thread-toggle{border:0;background:transparent;color:var(--v3-sub);padding:8px 0 2px;font-weight:700;cursor:pointer}.thread-toggle:hover{color:var(--v3-text)}.message-thread__replies{border-left:1px solid var(--v3-border)!important;margin-left:18px!important;padding-left:16px!important}.v3-status{position:fixed;z-index:40;left:0;right:0;bottom:0;height:38px;display:flex;align-items:center;padding:0 max(18px,calc((100vw - 980px)/2));background:var(--v3-panel);border-top:1px solid var(--v3-border);font-size:12px;color:var(--v3-sub);cursor:pointer}.v3-status-detail{position:fixed;z-index:39;left:50%;bottom:38px;transform:translateX(-50%);width:min(760px,calc(100% - 28px));max-height:45vh;overflow:auto;background:var(--v3-panel);border:1px solid var(--v3-border);border-bottom:0;border-radius:16px 16px 0 0;padding:16px;box-shadow:var(--v3-shadow);font:12px/1.55 var(--mono);white-space:pre-wrap}.v3-start{max-width:520px;margin:12vh auto;padding:28px;background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:24px;box-shadow:var(--v3-shadow)}.v3-start h1{margin:0 0 8px;font-size:32px;letter-spacing:-.04em}.v3-start p{color:var(--v3-sub);line-height:1.5}.v3-start-grid{display:grid;gap:12px}.v3-start input{background:var(--v3-bg);color:var(--v3-text);border-color:var(--v3-border)}.v3-open{height:48px;border:0;border-radius:12px;background:var(--v3-text);color:var(--v3-bg);font-weight:800;cursor:pointer}.v3-blocks{display:grid;gap:8px}.v3-block{background:var(--v3-panel);border:1px solid var(--v3-border);border-radius:14px;padding:12px 14px}.v3-block summary{cursor:pointer;font-weight:750}.v3-block pre{overflow:auto;color:var(--v3-sub);white-space:pre-wrap}.message-inline-media{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:10px}.message-inline-media:has(>a:only-child){grid-template-columns:minmax(0,1fr)}.message-inline-media a{display:block;overflow:hidden;border-radius:12px;border:1px solid var(--v3-border);background:var(--v3-bg)}.message-inline-media img{display:block;width:100%;max-height:520px;object-fit:contain;background:var(--v3-bg)}@media(max-width:520px){.message-inline-media{grid-template-columns:1fr}.message-inline-media img{max-height:70vh}}
|
||||
.verification-view{position:fixed;inset:0;z-index:80;background:var(--v3-bg);color:var(--v3-text);overflow:auto;padding:0 0 70px}
|
||||
.verification-head{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:12px;padding:14px max(16px,calc((100vw - 900px)/2));background:var(--v3-panel);border-bottom:1px solid var(--v3-border)}
|
||||
.verification-head h2{margin:0;font-size:18px}.verification-body{width:min(900px,calc(100% - 28px));margin:20px auto;display:grid;gap:14px}
|
||||
@@ -1553,7 +1553,7 @@ var solanaWeb3=function(exports){"use strict";function getDefaultExportFromCjs(x
|
||||
};
|
||||
}
|
||||
|
||||
function parseShineUserPda(dataBytes) {
|
||||
function parseShineUserPdaLegacy(dataBytes) {
|
||||
const bytes = dataBytes instanceof Uint8Array ? dataBytes : new Uint8Array(dataBytes || []);
|
||||
const reader = makeReader(bytes);
|
||||
const magic = new TextDecoder().decode(reader.readBytes(5));
|
||||
@@ -1717,6 +1717,138 @@ var solanaWeb3=function(exports){"use strict";function getDefaultExportFromCjs(x
|
||||
};
|
||||
}
|
||||
|
||||
function parseShineUserPdaV12(dataBytes) {
|
||||
const bytes = dataBytes instanceof Uint8Array ? dataBytes : new Uint8Array(dataBytes || []);
|
||||
const reader = makeReader(bytes);
|
||||
const magic = new TextDecoder().decode(reader.readBytes(5));
|
||||
if (magic !== MAGIC) throw new Error('Некорректный формат PDA: magic не совпадает');
|
||||
const formatMajor = reader.readU8();
|
||||
const formatMinor = reader.readU8();
|
||||
const recordLen = reader.readU16();
|
||||
if (formatMajor !== 1 || formatMinor !== 2 || recordLen < 73 || recordLen > bytes.length) {
|
||||
throw new Error('Некорректный формат PDA 1.2');
|
||||
}
|
||||
const createdAtMs = reader.readU64();
|
||||
const updatedAtMs = reader.readU64();
|
||||
const recordNumber = reader.readU32();
|
||||
const prevRecordHash = reader.readBytes(32);
|
||||
const login = reader.readStrU8();
|
||||
const blocksCount = reader.readU8();
|
||||
|
||||
let rootKey = null;
|
||||
let clientKey = null;
|
||||
let forks = null;
|
||||
let serverProfile = null;
|
||||
let accessServers = [];
|
||||
|
||||
for (let index = 0; index < blocksCount; index += 1) {
|
||||
const blockType = reader.readU8();
|
||||
const blockVersion = reader.readU8();
|
||||
if (blockType === BLOCK_TYPE_ROOT_KEY) {
|
||||
if (blockVersion !== 0) throw new Error('Неизвестная версия RootKeyBlock');
|
||||
rootKey = reader.readBytes(32);
|
||||
continue;
|
||||
}
|
||||
if (blockType === BLOCK_TYPE_CLIENT_KEY) {
|
||||
if (blockVersion !== 0) throw new Error('Неизвестная версия ClientKeyBlock');
|
||||
clientKey = reader.readBytes(32);
|
||||
continue;
|
||||
}
|
||||
|
||||
const payloadLen = reader.readU16();
|
||||
const payload = makeReader(reader.readBytes(payloadLen));
|
||||
if (blockVersion !== 0) continue;
|
||||
|
||||
if (blockType === BLOCK_TYPE_BLOCKCHAIN_REGISTRY) {
|
||||
const count = payload.readU16();
|
||||
if (count < 1) throw new Error('BlockchainRegistry 1.2 пуст');
|
||||
forks = [];
|
||||
for (let j = 0; j < count; j += 1) {
|
||||
forks.push({
|
||||
blockchainKey: payload.readBytes(32),
|
||||
paidLimitBytes: BigInt(payload.readU32()),
|
||||
});
|
||||
}
|
||||
} else if (blockType === BLOCK_TYPE_SERVER_PROFILE) {
|
||||
const count = payload.readU8();
|
||||
const addresses = [];
|
||||
for (let j = 0; j < count; j += 1) {
|
||||
addresses.push({
|
||||
addressFormatType: payload.readU8(),
|
||||
addressFormatVersion: payload.readU8(),
|
||||
address: payload.readStrU8(),
|
||||
});
|
||||
}
|
||||
const first = addresses[0] || null;
|
||||
serverProfile = first ? {
|
||||
blockVersion,
|
||||
addressFormatType: first.addressFormatType,
|
||||
addressFormatVersion: first.addressFormatVersion,
|
||||
serverAddress: first.address,
|
||||
addresses,
|
||||
syncServers: [],
|
||||
} : null;
|
||||
} else if (blockType === BLOCK_TYPE_ACCESS_SERVERS) {
|
||||
const count = payload.readU8();
|
||||
if (count > 1) throw new Error('PDA 1.2 допускает максимум один access server');
|
||||
accessServers = [];
|
||||
for (let j = 0; j < count; j += 1) accessServers.push(payload.readStrU8());
|
||||
}
|
||||
}
|
||||
|
||||
if (!rootKey || !clientKey || !forks || forks.length < 1) {
|
||||
throw new Error('В PDA 1.2 отсутствуют обязательные блоки shine_users');
|
||||
}
|
||||
const active = forks[forks.length - 1];
|
||||
const blockchain = {
|
||||
blockchainType: 1,
|
||||
blockchainName: `${String(login).toLowerCase()}-${String(forks.length).padStart(3, '0')}`,
|
||||
blockchainPublicKey: active.blockchainKey,
|
||||
paidLimitBytes: active.paidLimitBytes,
|
||||
usedBytes: 0n,
|
||||
lastBlockNumber: 0,
|
||||
lastBlockHash: new Uint8Array(32),
|
||||
lastBlockSignature: new Uint8Array(64),
|
||||
arweaveTxId: '',
|
||||
};
|
||||
const signature = bytes.slice(recordLen - 64, recordLen);
|
||||
return {
|
||||
formatMajor,
|
||||
formatMinor,
|
||||
recordLen,
|
||||
createdAtMs,
|
||||
updatedAtMs,
|
||||
recordNumber,
|
||||
prevRecordHash,
|
||||
login,
|
||||
blocksCount,
|
||||
recoveryKey: null,
|
||||
rootKey,
|
||||
clientKey,
|
||||
forks,
|
||||
blockchain,
|
||||
serverProfile,
|
||||
accessServers,
|
||||
sessionsMode: DEFAULT_SESSIONS_MODE,
|
||||
sessions: [],
|
||||
trustedCount: 0,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
function parseShineUserPda(dataBytes) {
|
||||
const bytes = dataBytes instanceof Uint8Array ? dataBytes : new Uint8Array(dataBytes || []);
|
||||
if (bytes.length < 9) throw new Error('Повреждённый формат PDA');
|
||||
const magic = new TextDecoder().decode(bytes.slice(0, 5));
|
||||
if (magic !== MAGIC) throw new Error('Некорректный формат PDA: magic не совпадает');
|
||||
const formatMajor = bytes[5];
|
||||
const formatMinor = bytes[6];
|
||||
if (formatMajor === 1 && formatMinor === 2) return parseShineUserPdaV12(bytes);
|
||||
if (formatMajor === 1 && formatMinor === 0) return parseShineUserPdaLegacy(bytes);
|
||||
throw new Error(`Неподдерживаемый формат PDA ${formatMajor}.${formatMinor}`);
|
||||
}
|
||||
|
||||
|
||||
async function digestSha256(bytes) {
|
||||
const input = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []);
|
||||
const hash = await crypto.subtle.digest('SHA-256', input);
|
||||
@@ -2288,7 +2420,7 @@ var solanaWeb3=function(exports){"use strict";function getDefaultExportFromCjs(x
|
||||
async function* queryArweaveChannelPages(gatewayBaseUrl, blockchainKey, channelName) {
|
||||
const gateway = baseUrl(gatewayBaseUrl);
|
||||
const ownerAddress = await arweaveOwnerAddressFromBlockchainKey(blockchainKey);
|
||||
// Author-first and transport-agnostic (Turbo/direct Arweave): discover user DataItems by owner + App=test5590; channel/c_test5590 is only a local view filter.
|
||||
// Author-first: channel is a local view filter, never an Arweave query filter.
|
||||
const tags = [{ name: 'App', values: [SHINE_ARWEAVE_APP_TAG] }];
|
||||
const query = `query($owners:[String!],$tags:[TagFilter!],$after:String){transactions(first:100,after:$after,owners:$owners,tags:$tags,sort:HEIGHT_DESC){pageInfo{hasNextPage}edges{cursor node{id owner{address key} block{height timestamp} bundledIn{id} tags{name value}}}}}`;
|
||||
let after = null;
|
||||
@@ -2476,7 +2608,7 @@ var solanaWeb3=function(exports){"use strict";function getDefaultExportFromCjs(x
|
||||
signatureHex: bytesToHex(parsed.signature),
|
||||
},
|
||||
keys: {
|
||||
recoveryKeyBase58: bytesToBase58(parsed.recoveryKey),
|
||||
recoveryKeyBase58: parsed.recoveryKey ? bytesToBase58(parsed.recoveryKey) : '',
|
||||
rootKeyBase58: bytesToBase58(parsed.rootKey),
|
||||
clientKeyBase58: bytesToBase58(parsed.clientKey),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user