import { PublicKey, Transaction, VersionedTransaction } from './js/lib/vendor/solana-publickey-bundle.js'; const PAGE_REQUEST = 'shine-wallet-page-request'; const PAGE_RESPONSE = 'shine-wallet-page-response'; const PAGE_MESSAGE_TARGET_ORIGIN = '*'; const STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet'; const STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready'; const SOLANA_CHAINS = ['solana:mainnet', 'solana:devnet', 'solana:testnet']; const SOLANA_STANDARD_FEATURES = ['solana:signTransaction']; const WALLET_ICON = `data:image/svg+xml;base64,${btoa( '' )}`; function bytesToBase64(bytes) { let binary = ''; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) { const slice = bytes.subarray(i, i + chunk); binary += String.fromCharCode(...slice); } return btoa(binary); } function base64ToBytes(value) { const binary = atob(String(value || '').trim()); const out = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i += 1) { out[i] = binary.charCodeAt(i); } return out; } function createProviderError(message, code = '') { const error = new Error(String(message || 'Wallet provider error')); if (code === 'USER_REJECTED' || code === 'NOT_TRUSTED') { error.code = 4001; } else if (code) { error.code = code; } return error; } function summarizeTransaction(transaction) { const summary = { kind: 'legacy', instructionCount: 0, accountCount: 0, feePayer: '', recentBlockhash: '', programs: [], }; if (!transaction) return summary; const isVersioned = typeof transaction?.version === 'number' || transaction instanceof VersionedTransaction; summary.kind = isVersioned ? `versioned:${String(transaction.version)}` : 'legacy'; summary.feePayer = String(transaction?.feePayer?.toBase58?.() || '').trim(); summary.recentBlockhash = String(transaction?.recentBlockhash || transaction?.message?.recentBlockhash || '').trim(); if (isVersioned) { const message = transaction?.message || {}; const staticKeys = Array.isArray(message?.staticAccountKeys) ? message.staticAccountKeys : []; const instructions = Array.isArray(message?.compiledInstructions) ? message.compiledInstructions : []; summary.instructionCount = instructions.length; summary.accountCount = staticKeys.length; summary.programs = instructions .map((instruction) => staticKeys[instruction?.programIdIndex]?.toBase58?.() || '') .filter(Boolean) .slice(0, 5); return summary; } const instructions = Array.isArray(transaction?.instructions) ? transaction.instructions : []; summary.instructionCount = instructions.length; summary.accountCount = Array.isArray(transaction?.signatures) ? transaction.signatures.length : 0; summary.programs = instructions .map((instruction) => instruction?.programId?.toBase58?.() || '') .filter(Boolean) .slice(0, 5); return summary; } function createRequest(method, params = {}) { const id = `shine-wallet-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; return new Promise((resolve, reject) => { const onMessage = (event) => { if (event.source !== window) return; const data = event.data || {}; if (data?.target !== PAGE_RESPONSE || String(data?.id || '') !== id) return; window.removeEventListener('message', onMessage); if (!data?.ok) { reject(createProviderError(data?.error || 'Wallet request failed', String(data?.code || ''))); return; } resolve(data?.result || {}); }; window.addEventListener('message', onMessage); window.postMessage({ target: PAGE_REQUEST, id, method, params, }, PAGE_MESSAGE_TARGET_ORIGIN); }); } function serializeTransactionBase64(transaction) { if (!transaction || typeof transaction.serialize !== 'function') { throw createProviderError('Unsupported transaction object', 'UNSUPPORTED_TRANSACTION'); } let raw; try { raw = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }); } catch { raw = transaction.serialize(); } const bytes = raw instanceof Uint8Array ? raw : new Uint8Array(raw); return bytesToBase64(bytes); } function deserializeSignedTransaction(base64, originalTransaction) { const bytes = base64ToBytes(base64); const ctor = originalTransaction?.constructor; if (ctor && typeof ctor.deserialize === 'function') { return ctor.deserialize(bytes); } if (ctor && typeof ctor.from === 'function') { return ctor.from(bytes); } if (typeof originalTransaction?.version === 'number') { return VersionedTransaction.deserialize(bytes); } return Transaction.from(bytes); } class ShineWalletAccount { constructor(publicKeyBase58) { this.address = publicKeyBase58; this.publicKey = new Uint8Array(new PublicKey(publicKeyBase58).toBytes()); this.chains = SOLANA_CHAINS.slice(); this.features = SOLANA_STANDARD_FEATURES.slice(); this.label = 'SHiNE Wallet'; this.icon = WALLET_ICON; } } class ShineProviderCore { constructor() { this.publicKey = null; this.isConnected = false; this._legacyListeners = new Map(); this._standardListeners = new Set(); this._accounts = []; } get publicKeyBase58() { return this.publicKey?.toBase58?.() || ''; } get standardAccounts() { return this._accounts.slice(); } async connect(options = {}) { const onlyIfTrusted = !!options?.onlyIfTrusted || !!options?.silent; const result = await createRequest('connect', { onlyIfTrusted }); const nextKey = new PublicKey(String(result?.publicKeyBase58 || '').trim()); this.publicKey = nextKey; this.isConnected = true; this._accounts = [new ShineWalletAccount(nextKey.toBase58())]; this.emitLegacy('connect', nextKey); this.emitLegacy('accountChanged', nextKey); this.emitStandardChange(); return { publicKey: nextKey, accounts: this.standardAccounts, }; } async disconnect() { await createRequest('disconnect', {}); this.isConnected = false; this.publicKey = null; this._accounts = []; this.emitLegacy('disconnect'); this.emitLegacy('accountChanged', null); this.emitStandardChange(); } async signTransaction(transaction, comment = '') { if (!this.publicKey) { await this.connect(); } const transactionBase64 = serializeTransactionBase64(transaction); const transactionSummary = summarizeTransaction(transaction); const result = await createRequest('signTransaction', { publicKeyBase58: this.publicKeyBase58, transactionBase64, comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`, transactionSummary, }); return deserializeSignedTransaction(String(result?.signedTransactionBase64 || ''), transaction); } async signTransactionBytes(transactionBytes, comment = '') { if (!this.publicKey) { await this.connect(); } const transactionSummary = { kind: 'raw-bytes', instructionCount: 0, accountCount: 0, feePayer: this.publicKeyBase58, recentBlockhash: '', programs: [], byteLength: Number(transactionBytes?.length || 0), }; const result = await createRequest('signTransaction', { publicKeyBase58: this.publicKeyBase58, transactionBase64: bytesToBase64(transactionBytes), comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`, transactionSummary, }); return base64ToBytes(String(result?.signedTransactionBase64 || '').trim()); } onLegacy(event, handler) { const key = String(event || ''); if (!this._legacyListeners.has(key)) { this._legacyListeners.set(key, new Set()); } this._legacyListeners.get(key).add(handler); return this; } offLegacy(event, handler) { const key = String(event || ''); const bucket = this._legacyListeners.get(key); if (!bucket) return this; bucket.delete(handler); if (!bucket.size) this._legacyListeners.delete(key); return this; } emitLegacy(event, payload) { const bucket = this._legacyListeners.get(String(event || '')); if (!bucket?.size) return; for (const handler of [...bucket]) { try { handler(payload); } catch {} } } onStandardChange(listener) { this._standardListeners.add(listener); return () => { this._standardListeners.delete(listener); }; } emitStandardChange() { const properties = { accounts: this.standardAccounts }; for (const listener of [...this._standardListeners]) { try { listener(properties); } catch {} } } } class ShineSolanaProvider { constructor(core) { this.core = core; this.isSHiNE = true; this.isPhantom = true; } get publicKey() { return this.core.publicKey; } get isConnected() { return this.core.isConnected; } on(event, handler) { return this.core.onLegacy(event, handler); } off(event, handler) { return this.core.offLegacy(event, handler); } removeListener(event, handler) { return this.off(event, handler); } async connect(options = {}) { const result = await this.core.connect(options); return { publicKey: result.publicKey }; } async disconnect() { await this.core.disconnect(); } async signTransaction(transaction) { return this.core.signTransaction(transaction); } async signAllTransactions(transactions = []) { const list = Array.isArray(transactions) ? transactions : []; const outputs = []; for (const transaction of list) { outputs.push(await this.core.signTransaction(transaction)); } return outputs; } async request(args = {}) { const method = String(args?.method || ''); const params = args?.params; if (method === 'connect') { return this.connect(Array.isArray(params) ? params[0] : params || {}); } if (method === 'disconnect') { return this.disconnect(); } if (method === 'signTransaction') { const tx = Array.isArray(params) ? params[0] : params?.transaction || params; return this.signTransaction(tx); } if (method === 'signAllTransactions') { const transactions = Array.isArray(params) ? params : Array.isArray(params?.transactions) ? params.transactions : []; return this.signAllTransactions(transactions); } throw createProviderError(`Unsupported request method: ${method}`, 'UNSUPPORTED_METHOD'); } } class ShineStandardWallet { constructor(core) { this.core = core; this.version = '1.0.0'; this.name = 'SHiNE Wallet'; this.icon = WALLET_ICON; this.chains = SOLANA_CHAINS.slice(); this.features = { 'standard:connect': { version: '1.0.0', connect: async (input = {}) => { const result = await this.core.connect({ silent: !!input?.silent }); return { accounts: result.accounts }; }, }, 'standard:disconnect': { version: '1.0.0', disconnect: async () => { await this.core.disconnect(); }, }, 'standard:events': { version: '1.0.0', on: (event, listener) => { if (event !== 'change' || typeof listener !== 'function') { return () => {}; } return this.core.onStandardChange(listener); }, }, 'solana:signTransaction': { version: '1.0.0', supportedTransactionVersions: ['legacy', 0], signTransaction: async (...inputs) => { const outputs = []; for (const input of inputs) { const accountAddress = String(input?.account?.address || '').trim(); if (accountAddress && this.core.publicKeyBase58 && accountAddress !== this.core.publicKeyBase58) { throw createProviderError('Requested account does not match current wallet account', 'ACCOUNT_MISMATCH'); } const comment = `Site ${window.location.origin} requested transaction signature`; const signedTransaction = await this.core.signTransactionBytes(new Uint8Array(input.transaction), comment); outputs.push({ signedTransaction }); } return outputs; }, }, }; } get accounts() { return this.core.standardAccounts; } } function registerStandardWallet(wallet) { const callback = ({ register }) => register(wallet); try { window.dispatchEvent(new CustomEvent(STANDARD_REGISTER_EVENT, { detail: callback })); } catch (error) { console.error('wallet-standard register dispatch failed', error); } try { window.addEventListener(STANDARD_APP_READY_EVENT, ({ detail }) => { try { callback(detail); } catch (error) { console.error('wallet-standard app-ready callback failed', error); } }); } catch (error) { console.error('wallet-standard app-ready listener failed', error); } try { window.navigator.wallets = window.navigator.wallets || []; window.navigator.wallets.push(callback); } catch {} } const core = new ShineProviderCore(); const legacyProvider = new ShineSolanaProvider(core); const standardWallet = new ShineStandardWallet(core); registerStandardWallet(standardWallet); if (!window.solana) { window.solana = legacyProvider; window.phantom = window.phantom || {}; window.phantom.solana = legacyProvider; window.dispatchEvent(new Event('solana#initialized')); }