@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
JavaScript
;
// 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}`);
}
}