import { PublicKey, Transaction } from '@solana/web3.js'; import type { WalletAdapter } from './types'; /** Convert an MWA address (base64 string or Uint8Array) to a PublicKey */ function toPublicKey(address: string | Uint8Array): PublicKey { if (address instanceof Uint8Array) { return new PublicKey(address); } // MWA protocol returns addresses as base64 — decode to bytes const bytes = Uint8Array.from(atob(address), (c) => c.charCodeAt(0)); return new PublicKey(bytes); } /** The `transact` function signature from @solana-mobile/mobile-wallet-adapter-protocol-web3js */ export type MwaTransactFn = ( callback: (wallet: any) => Promise, ) => Promise; export interface MwaAdapterConfig { /** The MWA `transact` function — import it from @solana-mobile/mobile-wallet-adapter-protocol-web3js */ transact: MwaTransactFn; appIdentity: { name: string; uri?: string; icon?: string; }; cluster?: string; onAuthTokenChange?: (token: string | null) => void; } /** * Mobile Wallet Adapter implementation. * Wraps @solana-mobile/mobile-wallet-adapter-protocol-web3js. */ export class MwaWalletAdapter implements WalletAdapter { private _publicKey: PublicKey | null = null; private _connected = false; private _authToken: string | null = null; /** The raw base64-encoded address from MWA authorize — needed for signMessages */ private _mwaAddress: string | null = null; private readonly config: MwaAdapterConfig; private readonly transact: MwaTransactFn; constructor(config: MwaAdapterConfig) { this.config = config; this.transact = config.transact; } get publicKey(): PublicKey | null { return this._publicKey; } get connected(): boolean { return this._connected; } get authToken(): string | null { return this._authToken; } setAuthToken(token: string | null): void { this._authToken = token; } /** * Restore a previous session silently (no wallet interaction). * Sets the public key and auth token so the adapter appears connected. * The next signing operation will reauthorize with Phantom. */ restoreSession(token: string, walletAddressBase58: string): void { this._authToken = token; this._publicKey = new PublicKey(walletAddressBase58); this._connected = true; } /** * Connect to a mobile wallet. Call this before any signing. * Tries reauthorize first (if we have a saved token), then falls back to * a fresh authorize in a SEPARATE transact session. */ async connect(): Promise { // Try reauthorize with saved token in its own transact session if (this._authToken) { try { await this.transact(async (wallet) => { const authResult = await wallet.reauthorize({ auth_token: this._authToken }); this.applyAuthResult(authResult); }); return; } catch { // Stale token — clear it and do fresh authorize below console.log('[Dubs:MWA] reauthorize failed, clearing token and doing fresh authorize'); this._authToken = null; this.config.onAuthTokenChange?.(null); } } // Fresh authorize in a new transact session await this.transact(async (wallet) => { const authResult = await wallet.authorize({ identity: this.config.appIdentity, cluster: this.config.cluster || 'mainnet-beta', }); this.applyAuthResult(authResult); }); } /** * Disconnect and clear auth token. */ disconnect(): void { this._publicKey = null; this._connected = false; this._authToken = null; this._mwaAddress = null; this.config.onAuthTokenChange?.(null); } async signTransaction(transaction: Transaction): Promise { if (!this._connected) throw new Error('Wallet not connected'); const signed = await this.transact(async (wallet) => { const reauth = await wallet.reauthorize({ auth_token: this._authToken }); this.applyAuthResult(reauth); const result = await wallet.signTransactions({ transactions: [transaction], }); return result[0]; }); return signed; } async signMessage(message: Uint8Array): Promise { if (!this._connected || !this._publicKey) throw new Error('Wallet not connected'); const sig = await this.transact(async (wallet) => { const reauth = await wallet.reauthorize({ auth_token: this._authToken }); this.applyAuthResult(reauth); const result = await wallet.signMessages({ addresses: [reauth.accounts[0].address], payloads: [message], }); return result[0]; }); return sig instanceof Uint8Array ? sig : new Uint8Array(sig); } async signAndSendTransaction(transaction: Transaction): Promise { if (!this._connected) throw new Error('Wallet not connected'); let signature: any; try { signature = await this.transact(async (wallet) => { const reauth = await wallet.reauthorize({ auth_token: this._authToken }); this.applyAuthResult(reauth); const result = await wallet.signAndSendTransactions({ transactions: [transaction], }); return result[0]; }); } catch (err: any) { console.error('[Dubs:MWA] ===== signAndSendTransaction ERROR DUMP ====='); console.error('[Dubs:MWA] typeof:', typeof err); console.error('[Dubs:MWA] value:', err); console.error('[Dubs:MWA] message:', err?.message); console.error('[Dubs:MWA] name:', err?.name); console.error('[Dubs:MWA] code:', err?.code); console.error('[Dubs:MWA] cause:', err?.cause); console.error('[Dubs:MWA] stack:', err?.stack); console.error('[Dubs:MWA] JSON:', (() => { try { return JSON.stringify(err, Object.getOwnPropertyNames(err ?? {})); } catch { return 'unstringifiable'; } })()); console.error('[Dubs:MWA] ===== END DUMP ====='); throw err; } // MWA returns Uint8Array signature — convert to base58 string if (signature instanceof Uint8Array) { return new PublicKey(signature).toBase58(); } return String(signature); } private applyAuthResult(authResult: any): void { this._mwaAddress = authResult.accounts[0].address as string; this._publicKey = toPublicKey(this._mwaAddress); this._authToken = authResult.auth_token; this._connected = true; this.config.onAuthTokenChange?.(this._authToken); } }