import { registerUserOnSolana as registerUserOnSolanaShared } from './shine-user-pda-service.js'; import { SHINE_USERS_PROGRAM_ID, SHINE_LOGIN_GUARD_PROGRAM_ID, } from '../solana-programs.js'; const CLASSIFY_LOGIN_DISCRIMINATOR = new Uint8Array([112, 97, 152, 32, 255, 73, 108, 86]); const PRECHECK_SIM_PAYER = 'FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P'; let solanaLibPromise = null; function loadSolanaLib() { if (!solanaLibPromise) solanaLibPromise = import('https://esm.sh/@solana/web3.js@1.98.4?bundle'); return solanaLibPromise; } function pushU32LE(buf, v) { const n = v >>> 0; buf.push(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, (n >> 24) & 0xFF); } class BorshBuf { constructor() { this._b = []; } u8(v) { this._b.push(v & 0xFF); } u32(v) { pushU32LE(this._b, v); } str(s) { const enc = new TextEncoder().encode(s); this.u32(enc.length); for (const x of enc) this._b.push(x); } raw(bytes) { for (const x of bytes) this._b.push(x); } result() { return new Uint8Array(this._b); } } function serializeClassifyLoginArgs(login) { const b = new BorshBuf(); b.raw(CLASSIFY_LOGIN_DISCRIMINATOR); b.str(String(login || '')); return b.result(); } function decodeU32FromB64(rawB64) { const bytes = Uint8Array.from(atob(rawB64), (ch) => ch.charCodeAt(0)); if (bytes.length < 4) throw new Error('LOGIN_GUARD_BAD_RETURN_DATA'); return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, true); } function safeJson(value) { try { return JSON.stringify(value); } catch { return String(value); } } export 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: ${logs.join(' | ')}`); } const errObj = error?.value?.err || error?.err || error?.data?.err; if (errObj) { parts.push(`Err: ${safeJson(errObj)}`); } if (!parts.length) return 'unknown'; return parts.join(' :: '); } export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) { const solana = await loadSolanaLib(); const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed'); const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID); const payer = new solana.PublicKey(PRECHECK_SIM_PAYER); const ix = new solana.TransactionInstruction({ programId: loginGuardProgram, keys: [{ pubkey: payer, isSigner: true, isWritable: false }], data: serializeClassifyLoginArgs(String(login || '').toLowerCase()), }); const { blockhash } = await connection.getLatestBlockhash('confirmed'); const v0Message = new solana.TransactionMessage({ payerKey: payer, recentBlockhash: blockhash, instructions: [ix], }).compileToV0Message(); const tx = new solana.VersionedTransaction(v0Message); const sim = await connection.simulateTransaction(tx, { commitment: 'confirmed', sigVerify: false, replaceRecentBlockhash: true, }); if (sim?.value?.err) { const simErr = new Error(`LOGIN_GUARD_SIMULATION_FAILED: ${safeJson(sim.value.err)}`); simErr.logs = sim?.value?.logs || []; simErr.err = sim?.value?.err; throw simErr; } const returnData = sim?.value?.returnData; if (!returnData || returnData.programId !== SHINE_LOGIN_GUARD_PROGRAM_ID) { throw new Error('LOGIN_GUARD_BAD_RETURN_DATA'); } const classValue = decodeU32FromB64(returnData.data?.[0] || ''); if (classValue === 0) return { classCode: 0, className: 'free' }; if (classValue === 1) return { classCode: 1, className: 'premium' }; if (classValue === 2) return { classCode: 2, className: 'company' }; return { classCode: classValue, className: 'unknown' }; } export async function checkLoginExistsOnSolana({ login, solanaEndpoint }) { const solana = await loadSolanaLib(); const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed'); const usersProgram = new solana.PublicKey(SHINE_USERS_PROGRAM_ID); const enc = new TextEncoder(); const loginNorm = String(login || '').trim().toLowerCase(); if (!loginNorm) { throw new Error('EMPTY_LOGIN'); } const [userPda] = solana.PublicKey.findProgramAddressSync( [enc.encode('login='), enc.encode(loginNorm)], usersProgram, ); const ai = await connection.getAccountInfo(userPda, 'confirmed'); return { exists: !!ai, userPda: userPda.toBase58() }; } export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint }) { return registerUserOnSolanaShared({ login, keyBundle, solanaEndpoint }); }