/** * Service Client Interface * * Interface for HTTP client that communicates with the Gas-Free backend service * * @module IServiceClient */ import type { FeeQuote, FeeQuoteRequest, SubmitResult, SubmitPSBTRequest, VerificationResult, VerifyTransferRequest, } from '../types'; /** * IServiceClient interface * * Defines the contract for communicating with the Gas-Free backend service. * Implementations should handle HTTP communication, retries, timeouts, and error handling. */ export interface IServiceClient { /** * Generate a fee quote for a Gas-Free transfer * * Requests a fee quote from the backend service, which includes mining UTXO details * and the fee amount required for the transfer. * * @param request - Fee quote request parameters * @returns Promise resolving to a fee quote with mining UTXOs * @throws {ServiceUnavailableError} If backend service is unavailable * @throws {InvalidPSBTError} If request validation fails * @throws {GasFreeError} For other service errors * * @example * ```typescript * const quote = await client.generateFeeQuote({ * rgb_invoice: 'rgb1...', * amount: '1000', * user_utxos: [...], * user_pubkey: '02...' * }); * * console.log(quote.quote_id); // Use for submission * ``` */ generateFeeQuote(request: FeeQuoteRequest): Promise; /** * Submit the unsigned PSBT and RGB consignment to the service for co-signing * * Sends the unsigned PSBT (returned by sendBegin()) and the RGB consignment to the * backend service. The service: * 1. Verifies the consignment cryptographically — confirms it contains an RGB * allocation matching the serviceFeeInvoice and serviceFeeAmount. * 2. Co-signs the mining input using its own key. * 3. Returns the partially-signed PSBT with its signature attached. * * The user's RGB inputs are still unsigned at this point. After receiving the * service-signed PSBT, the SDK calls signPsbt() locally to sign the RGB inputs, * then broadcasts via sendEnd(). * * @param request - Sign PSBT request parameters (unsigned PSBT + consignment) * @returns Promise resolving to the service-signed PSBT * @throws {QuoteExpiredError} If the quote has expired * @throws {ServiceUnavailableError} If backend service is unavailable * @throws {InvalidPSBTError} If PSBT or consignment validation fails * @throws {GasFreeError} For other service errors * * @example * ```typescript * const result = await client.signPsbt({ * quoteId: quote.quoteId, * psbtBase64: unsignedPsbtBase64, // from sendBegin() * consignmentBase64: consignmentBase64, * rgbInvoice: 'rgb:~/~/...' // serviceFeeInvoice * }); * * console.log(result.serviceSignedPsbt); // Partially signed — user signs next * ``` */ signPsbt(request: SubmitPSBTRequest): Promise; /** * Verify a transfer status * * Verifies that a transaction has been broadcast and that the service has received * the RGB asset payment. Can be called with transferSuccess=false to release resources * if the SDK failed to broadcast. * * @param request - Verify transfer request parameters * @returns Promise resolving to verification result with transfer status * @throws {ServiceUnavailableError} If backend service is unavailable * @throws {GasFreeError} For other service errors * * @example * ```typescript * // Success case * const verification = await client.verifyTransfer({ * quoteId: quote.quoteId, * transferSuccess: true, * signedPsbtBase64: signedPsbt, * txid: result.transactionId * }); * * if (verification.status === 'verified') { * console.log('Transfer verified!'); * } * * // Failure case * await client.verifyTransfer({ * quoteId: quote.quoteId, * transferSuccess: false, * failureReason: 'SDK failed to finalize PSBT' * }); * ``` */ verifyTransfer(request: VerifyTransferRequest): Promise; /** * Get the current configuration * * @returns Readonly client configuration */ getConfig(): Readonly; /** * Update timeout for requests * * @param timeout - New timeout in milliseconds */ setTimeout(timeout: number): void; /** * Cleanup and release resources * * Should be called when the client is no longer needed. */ cleanup(): Promise; } /** * Service Client Configuration * * Configuration options for the service client. */ export interface ServiceClientConfig { /** * API key for authentication */ apiKey: string; /** * Base URL of the Gas-Free backend service * * @example 'https://api.orbis1.com' */ baseUrl: string; /** * Request timeout in milliseconds * * @default 30000 (30 seconds) */ timeout?: number; /** * Maximum number of retry attempts for failed requests * * @default 3 */ maxRetries?: number; /** * Initial retry delay in milliseconds (for exponential backoff) * * @default 1000 (1 second) */ retryDelay?: number; /** * Custom headers to include in all requests */ headers?: Record; } /** * Default service client configuration */ export const DEFAULT_SERVICE_CLIENT_CONFIG: Required< Omit > = { timeout: 30000, // 30 seconds maxRetries: 3, retryDelay: 1000, // 1 second }; /** * Validate service client configuration * * @param config - Configuration to validate * @throws {Error} If configuration is invalid */ export function validateServiceClientConfig(config: ServiceClientConfig): void { if (!config.apiKey || config.apiKey.trim().length === 0) { throw new Error('API key is required'); } if (!config.baseUrl || config.baseUrl.trim().length === 0) { throw new Error('Base URL is required'); } // // Validate URL format // try { // new URL(config.baseUrl); // } catch { // throw new Error('Invalid base URL format'); // } if ( config.timeout !== undefined && (config.timeout <= 0 || !Number.isFinite(config.timeout)) ) { throw new Error('Timeout must be a positive number'); } if ( config.maxRetries !== undefined && (config.maxRetries < 0 || !Number.isInteger(config.maxRetries)) ) { throw new Error('Max retries must be a non-negative integer'); } if ( config.retryDelay !== undefined && (config.retryDelay <= 0 || !Number.isFinite(config.retryDelay)) ) { throw new Error('Retry delay must be a positive number'); } } /** * Merge client configuration with defaults * * @param config - Partial configuration * @returns Complete configuration with defaults */ export function mergeServiceClientConfig( config: ServiceClientConfig ): Required { return { ...DEFAULT_SERVICE_CLIENT_CONFIG, ...config, headers: config.headers || {}, }; }