Новый протокол Solana PDA 1.2

This commit is contained in:
AidarKC
2026-09-26 11:01:14 +03:00
parent 6e5b57fd7c
commit c997057a37
47 changed files with 2392 additions and 4480 deletions
+137 -5
View File
@@ -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),
},