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;