import { useCallback } from 'react'; import { usePersSDK } from '../providers/PersSDKProvider'; import { useTransactionSigner, OnStatusUpdateFn } from './useTransactionSigner'; import type { TransactionRequestDTO, TransactionRequestResponseDTO, TransactionDTO, TransactionPaginationRequestDTO, PaginatedResponseDTO, TransactionIncludeRelation } from '@explorins/pers-sdk'; import type { TransactionQueryOptions } from '@explorins/pers-sdk/transaction'; import { needsSubmission } from '@explorins/pers-sdk/transaction'; // Re-export for consumers export type { TransactionQueryOptions } from '@explorins/pers-sdk/transaction'; /** * React hook for transaction operations in the PERS SDK * * Provides comprehensive transaction management including creation, retrieval, history, * and administrative operations. Supports pagination and CSV export functionality. * * @returns Transaction hook with methods for transaction management * * @example * ```typescript * function TransactionsComponent() { * const { * createTransaction, * getUserTransactionHistory * } = useTransactions(); * * const handleCreateTransaction = async (request) => { * try { * const result = await createTransaction(request); * console.log('Transaction created:', result); * // Handle signature URL if returned * } catch (error) { * console.error('Transaction failed:', error); * } * }; * * return ( *
* *
* ); * } * ``` */ export const useTransactions = () => { const { sdk, isInitialized, isAuthenticated } = usePersSDK(); const { signAndSubmitTransactionWithJWT, isSignerAvailable, currentStatus, statusMessage } = useTransactionSigner(); if (!isAuthenticated && isInitialized) { console.warn('SDK not authenticated. Some transaction operations may fail.'); } /** * Creates a new transaction in the system * * Automatically handles signature URLs by opening them in the device's browser. * The transaction may require user approval through external wallet applications. * * @param request - Transaction request data including amount, recipient, etc. * @returns Promise resolving to transaction response with potential actionable items * @throws Error if SDK is not initialized * * @example * ```typescript * const { createTransaction } = useTransactions(); * const request = { * amount: '100', * recipient: '0x123...', * tokenId: 'token-123' * }; * const result = await createTransaction(request, (status, message) => { * console.log(`Status: ${status} - ${message}`); * }); * console.log('Transaction created:', result); * ``` */ const createTransaction = useCallback(async (request: TransactionRequestDTO, onStatusUpdate?: OnStatusUpdateFn): Promise => { if (!isInitialized || !sdk) { throw new Error('SDK not initialized. Call initialize() first.'); } try { const result = await sdk.transactions.createTransaction(request); // If backend already signed custodially, return as-is so caller can build QR // from result.signatureData directly (no client signing needed) if (needsSubmission(result)) { return result; } // If client signer is unavailable, return as-is so caller can fall back to // result.actionable.actionUrl (external signing URL) if present if (!isSignerAvailable) { console.warn('[useTransactions] Transaction signer not available, returning result for caller to handle'); return result; } // Check if transaction requires client signing (contains actionable authToken) // Type assertion needed as TransactionRequestResponseDTO type may not include all dynamic properties const txToken = result?.actionable?.authToken; if (txToken) { try { // Automatically sign the transaction using the authToken const signingResult = await signAndSubmitTransactionWithJWT(txToken, onStatusUpdate); if (signingResult.success) { console.log('[useTransactions] Transaction signed successfully:', signingResult.transactionHash); // Return the original result - the transaction is now signed and will be processed return result; } else { console.error('[useTransactions] Transaction signing failed:', signingResult.error); // Don't throw error - return the original result so caller can handle signature URL manually return result; } } catch (signingError) { console.error('[useTransactions] Blockchain signing error:', signingError); // Don't throw error - return the original result so caller can handle signature URL manually return result; } } return result; } catch (error) { console.error('Failed to create transaction:', error); throw error; } }, [sdk, isInitialized, signAndSubmitTransactionWithJWT, isSignerAvailable]); /** * Retrieves a specific transaction by its ID with optional include relations * * @param transactionId - Unique identifier of the transaction * @param include - Optional relations to include (sender, recipient, business) for enriched entity data * @returns Promise resolving to transaction data or null if not found * @throws Error if SDK is not initialized * * @example * ```typescript * const { getTransactionById } = useTransactions(); * * // Basic retrieval * const transaction = await getTransactionById('txn-123'); * * // With enriched sender/recipient data * const enrichedTx = await getTransactionById('txn-123', ['sender', 'recipient', 'business']); * console.log('Sender:', enrichedTx?.included?.sender); * console.log('Business:', enrichedTx?.included?.engagedBusiness?.displayName); * ``` */ const getTransactionById = useCallback(async ( transactionId: string, include?: TransactionIncludeRelation[] ): Promise => { if (!isInitialized || !sdk) { throw new Error('SDK not initialized. Call initialize() first.'); } try { return await sdk.transactions.getTransactionById(transactionId, include); } catch (error) { console.error('Failed to fetch transaction:', error); throw error; } }, [sdk, isInitialized]); /** * Retrieves transaction history for the authenticated user with comprehensive filtering * * Supports filtering by role, status, type, business, token, and more. * Optionally enrich with related entities (sender, recipient, business). * * @param options - Query options including filters, pagination, and include relations * @returns Promise resolving to paginated array of user's transactions * @throws Error if SDK is not initialized * * @example * ```typescript * const { getUserTransactionHistory } = useTransactions(); * * // Simple: Get all transactions * const allTransactions = await getUserTransactionHistory(); * * // Filter by role (legacy support) * const sentTransactions = await getUserTransactionHistory({ role: 'SENDER' }); * * // Advanced filtering with include relations * const filtered = await getUserTransactionHistory({ * role: 'SENDER', * status: 'COMPLETED', * engagedBusinessId: 'business-123', * include: ['recipient', 'business'], * page: 1, * limit: 20 * }); * * // Access enriched data * filtered.data.forEach(tx => { * console.log('Recipient:', tx.included?.recipient); * console.log('Business:', tx.included?.engagedBusiness?.displayName); * }); * ``` */ const getUserTransactionHistory = useCallback(async ( options?: TransactionQueryOptions ): Promise> => { if (!isInitialized || !sdk) { throw new Error('SDK not initialized. Call initialize() first.'); } try { return await sdk.transactions.getUserTransactionHistory(options); } catch (error) { console.error('Failed to fetch transaction history:', error); throw error; } }, [sdk, isInitialized]); /** * Admin: Get paginated transactions with optional include relations * * @param params - Pagination and filtering parameters * @param include - Optional relations to include for enriched entity data * @returns Promise resolving to paginated transaction results * @throws Error if SDK is not initialized * * @example * ```typescript * const { getPaginatedTransactions } = useTransactions(); * * // Basic pagination * const result = await getPaginatedTransactions({ page: 1, limit: 50 }); * * // With include relations * const enrichedResult = await getPaginatedTransactions( * { page: 1, limit: 50, sortBy: 'createdAt', sortOrder: 'DESC' }, * include: ['sender', 'recipient', 'business'] * }); * * enrichedResult.data.forEach(tx => { * console.log('From:', tx.included?.sender); * console.log('To:', tx.included?.recipient); * }); * ``` */ const getPaginatedTransactions = useCallback(async ( options: TransactionPaginationRequestDTO & { include?: TransactionIncludeRelation[]; } ): Promise> => { if (!isInitialized || !sdk) { throw new Error('SDK not initialized. Call initialize() first.'); } try { const result = await sdk.transactions.getPaginatedTransactions(options); return result; } catch (error) { console.error('Failed to fetch paginated transactions:', error); throw error; } }, [sdk, isInitialized]); const exportTransactionsCSV = useCallback(async (): Promise => { if (!isInitialized || !sdk) { throw new Error('SDK not initialized. Call initialize() first.'); } try { const result = await sdk.transactions.exportTransactionsCSV(); return result; } catch (error) { console.error('Failed to export transactions CSV:', error); throw error; } }, [sdk, isInitialized]); return { createTransaction, getTransactionById, getUserTransactionHistory, getPaginatedTransactions, exportTransactionsCSV, isAvailable: isInitialized && !!sdk?.transactions, // Expose signing status for UI feedback signingStatus: currentStatus, signingStatusMessage: statusMessage, }; }; export type TransactionHook = ReturnType;