UNPKG

@tonyboyle/solana-wallet-universal-links-generator

Version:

A minimal, stateless TypeScript SDK for generating deep links to mobile Solana wallets

155 lines (154 loc) 6.11 kB
"use strict"; // Utility functions for Universal Solana Wallet Adapter Object.defineProperty(exports, "__esModule", { value: true }); exports.generateDeepLink = generateDeepLink; exports.parseCallback = parseCallback; exports.isSupportedWallet = isSupportedWallet; exports.getWalletMetadata = getWalletMetadata; exports.parseEncryptedCallback = parseEncryptedCallback; const encryption_1 = require("./encryption"); const types_1 = require("./types"); /** * Generates a deep link URL for wallet operations */ function generateDeepLink(wallet, method, params) { const baseUrl = getWalletBaseUrl(wallet); const queryParams = new URLSearchParams(); // Add all other parameters (excluding internal encryption parameters) Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { // Skip internal encryption parameters that shouldn't be in the URL if (key === 'dapp_private_key' || key === 'wallet_encryption_public_key') { return; } if (key === 'payload' && typeof value === 'object') { // Auto-encrypt payload if encryption parameters are provided const encryptedPayload = autoEncryptPayload(value, params); queryParams.append(key, encryptedPayload); } else { queryParams.append(key, String(value)); } } }); // Append the method endpoint to the base URL const methodEndpoint = method === 'connect' ? '/connect' : `/${method}`; return `${baseUrl}${methodEndpoint}?${queryParams.toString()}`; } /** * Gets the base URL for a specific wallet */ function getWalletBaseUrl(wallet) { const walletKey = wallet.toLowerCase(); const metadata = types_1.WALLET_METADATA[walletKey]; if (!metadata) { throw new Error(`Unsupported wallet: ${wallet}. Supported wallets: ${Object.keys(types_1.WALLET_METADATA).join(', ')}`); } // Return the base URL as-is (already includes version) return metadata.baseUrl; } /** * Automatically encrypts a payload if encryption parameters are available * According to Phantom docs: payload should be "an encrypted JSON string" * This means we encrypt the entire JSON string, not individual fields */ function autoEncryptPayload(payload, params) { // Check if we have the necessary encryption parameters const hasEncryptionParams = params.dapp_encryption_public_key && params.nonce && params.wallet_encryption_public_key && params.dapp_private_key; if (hasEncryptionParams) { try { // Convert payload to JSON string first, then encrypt the entire string const payloadJsonString = JSON.stringify(payload); // Use the encryption module to encrypt the JSON string const result = (0, encryption_1.encrypt)(payloadJsonString, // Encrypt the JSON string, not the object params.dapp_private_key || '', // This should be provided by the app params.wallet_encryption_public_key, params.nonce); return result.encrypted; } catch (error) { console.warn('Failed to encrypt payload, falling back to JSON:', error); return JSON.stringify(payload); } } else { // Fall back to JSON stringification if encryption params are missing console.warn('Encryption parameters missing. For security, provide dapp_private_key, wallet_public_key, and nonce.'); return JSON.stringify(payload); } } /** * Parses callback data from wallet redirects */ function parseCallback(url) { try { const urlObj = new URL(url); const method = urlObj.searchParams.get('ref') || ''; const data = urlObj.searchParams.get('data') || ''; const error = urlObj.searchParams.get('error') || undefined; return { method, data: data ? JSON.parse(decodeURIComponent(data)) : null, error: error ? JSON.parse(decodeURIComponent(error)) : undefined }; } catch (error) { throw new Error(`Failed to parse callback URL: ${error}`); } } /** * Validates if a wallet name is supported * * @param wallet - The wallet name to validate * @returns True if the wallet is supported */ function isSupportedWallet(wallet) { return Object.keys(types_1.WALLET_METADATA).includes(wallet.toLowerCase()); } /** * Gets metadata for a specific wallet * * @param wallet - The wallet name * @returns Wallet metadata or undefined if not supported */ function getWalletMetadata(wallet) { const walletKey = wallet.toLowerCase(); return types_1.WALLET_METADATA[walletKey]; } /** * Enhanced callback parser that handles encrypted responses * Use this when you expect encrypted data from the wallet */ function parseEncryptedCallback(url, dappPrivateKey, walletPublicKey) { try { const urlObj = new URL(url); const method = urlObj.searchParams.get('ref') || ''; const encryptedData = urlObj.searchParams.get('data') || ''; const error = urlObj.searchParams.get('error') || undefined; let data = null; if (encryptedData) { try { // Extract nonce from URL if not provided via encryptedData const nonceFromUrl = urlObj.searchParams.get('nonce'); if (!nonceFromUrl) { throw new Error('Nonce is required for decryption'); } // Decrypt the data using the encryption module data = (0, encryption_1.decrypt)(encryptedData, nonceFromUrl, dappPrivateKey, walletPublicKey); } catch (error) { throw new Error(`Failed to decrypt callback data: ${error}`); } } return { method, data, error: error ? JSON.parse(decodeURIComponent(error)) : undefined }; } catch (error) { throw new Error(`Failed to parse encrypted callback URL: ${error}`); } }