/** * Tron Wallet Discovery * * Discovers Tron wallets by checking injected providers on the window object. * Different wallets inject at different paths: * - TronLink: window.tronLink * - TokenPocket: window.tokenpocket * - Trust Wallet: window.trustWallet.tronLink * * Each provider has a `tronWeb` property containing the TronWeb API. */ import type { WalletMetadata } from '@meshconnect/uwc-types' import { BRIDGE_WALLET_POLL_MS } from './bridge-poll' export interface TronWeb { defaultAddress?: { base58?: string | false hex?: string | false } /** Async accessor for defaultAddress, used by bridge providers */ getDefaultAddress?(): Promise<{ base58?: string | false hex?: string | false }> trx: { sign(transaction: unknown): Promise signMessageV2(message: string): Promise sendRawTransaction(signedTransaction: unknown): Promise } transactionBuilder: { // eslint-disable-next-line @typescript-eslint/no-explicit-any sendTrx(to: string, amount: number, from: string): Promise triggerSmartContract( contractAddress: string, functionSelector: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any options: Record, // eslint-disable-next-line @typescript-eslint/no-explicit-any parameter: Array<{ type: string; value: any }>, issuerAddress: string // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise<{ transaction: any; result: { result: boolean } }> } toHex(message: string): string // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any } export interface TronProvider { ready: boolean request?(params: { method: string; params?: unknown }): Promise tronWeb: TronWeb // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any } export interface DetectedTronWallet { uuid: string name: string injectedId: string provider: TronProvider } /** * Resolves a nested property on the window object using dot notation */ function getNestedProperty( // eslint-disable-next-line @typescript-eslint/no-explicit-any obj: Record, path: string // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any { return path.split('.').reduce((acc, part) => acc?.[part], obj) } /** * Checks if a value looks like a Tron provider (has request and tronWeb) */ function isTronProvider(value: unknown): value is TronProvider { if (!value || typeof value !== 'object') return false const provider = value as Record return provider['tronWeb'] != null && 'request' in provider } /** * Generate a unique ID for a Tron wallet based on its injected path */ function generateWalletId(injectedId: string): string { return `tron-${injectedId}`.toLowerCase().replace(/\./g, '-') } /** * Checks all injectedId paths and returns any detected Tron wallets. */ function discoverTronProviders(injectedIds: string[]): DetectedTronWallet[] { const wallets: DetectedTronWallet[] = [] const seen = new Set() for (const injectedId of injectedIds) { const provider = getNestedProperty( window as unknown as Record, injectedId ) if (isTronProvider(provider) && !seen.has(injectedId)) { seen.add(injectedId) // Derive a human-readable name from the path const name = injectedId.split('.')[0] || injectedId wallets.push({ uuid: generateWalletId(injectedId), name, injectedId, provider }) } } return wallets } /** * Polls for bridge Tron wallets when UWCBridgeChildInitialized is true */ async function pollForBridgeTronWallets(): Promise { const pollInterval = 50 // Consumer poll budget — rationale + the must-exceed-child-budget invariant // are documented on BRIDGE_WALLET_POLL_MS. The child resolves the array (real // wallets or []) as soon as the parent answers, so this is a ceiling, not a // fixed wait. const maxPollTime = BRIDGE_WALLET_POLL_MS const startTime = Date.now() return new Promise(resolve => { const poll = () => { if (Date.now() - startTime > maxPollTime) { resolve([]) return } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - window.tronWallets is set by the bridge if (window.tronWallets && Array.isArray(window.tronWallets)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const bridgeWallets = window.tronWallets as DetectedTronWallet[] resolve(bridgeWallets) return } setTimeout(poll, pollInterval) } poll() }) } /** * Discovers Tron wallets by checking known injection paths on the window object. * When running inside an iframe with the UWC bridge, checks for bridge-proxied * wallets first. Otherwise checks injected providers on the window object. */ export async function getTronWallets( expectedWallets: WalletMetadata[] = [] ): Promise { try { const injectedIds = expectedWallets .map( w => w.extensionInjectedProvider?.namespaceMetaData?.tron?.injectedId ) .filter((id): id is string => !!id) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - window.UWCBridgeChildInitialized is set by the bridge const bridgeMode = window.UWCBridgeChildInitialized === true // Check for bridge-proxied wallets first (iframe mode) if (bridgeMode) { const wallets = await pollForBridgeTronWallets() return wallets.length > 0 ? wallets : [] } // Native discovery: check injected providers on the window object. const wallets = discoverTronProviders(injectedIds) return wallets } catch { // Return empty array if discovery fails return [] } }