import { sdkLogger } from '../logger' const LOG_PREFIX = '[Portal.waitForConfirmation]' export type SolanaConfirmationCommitment = | 'processed' | 'confirmed' | 'finalized' export type SolanaRequestFn = ( method: string, params: unknown[], network: string, ) => Promise export interface WaitForSolanaTxConfirmationOptions { pollIntervalMs: number timeoutMs: number commitment: SolanaConfirmationCommitment } const COMMITMENT_ORDER: Record = { processed: 0, confirmed: 1, finalized: 2, } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) } function statusMeetsCommitment( confirmationStatus: string | undefined, required: SolanaConfirmationCommitment, ): boolean { if (!confirmationStatus) return false const level = COMMITMENT_ORDER[confirmationStatus as SolanaConfirmationCommitment] const need = COMMITMENT_ORDER[required] if (level === undefined || need === undefined) return false return level >= need } interface SignatureStatusRow { err?: unknown confirmationStatus?: string } function parseSignatureStatusesResult(raw: unknown): SignatureStatusRow | null { if (raw == null || typeof raw !== 'object') return null const outer = raw as { value?: unknown; result?: unknown } const arr = outer.value ?? outer.result if (!Array.isArray(arr) || arr.length === 0) return null const first = arr[0] if (first == null || typeof first !== 'object') return null return first as SignatureStatusRow } /** * Polls Solana `getSignatureStatuses` via {@link request} (routed through the * iframe RPC proxy) until the signature reaches the requested commitment, * fails on-chain, or times out. */ export async function waitForSolanaTxConfirmation( signature: string, network: string, request: SolanaRequestFn, options: WaitForSolanaTxConfirmationOptions, ): Promise { const { pollIntervalMs, timeoutMs, commitment } = options sdkLogger.debug(`${LOG_PREFIX} waiting for Solana confirmation`, { signature, network, pollIntervalMs, timeoutMs, commitment, }) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { try { const raw = await request( 'getSignatureStatuses', [[signature], { searchTransactionHistory: true }], network, ) const rpcResponse = raw as { result?: unknown; error?: unknown } if (rpcResponse.error) { throw new Error( `${LOG_PREFIX} RPC error for getSignatureStatuses: ${JSON.stringify(rpcResponse.error)}`, ) } const statusRow = parseSignatureStatusesResult( rpcResponse.result ?? raw, ) if (statusRow != null) { if (statusRow.err != null) { sdkLogger.debug(`${LOG_PREFIX} Solana tx error on-chain`, { signature, network, err: statusRow.err, }) return false } if (statusMeetsCommitment(statusRow.confirmationStatus, commitment)) { sdkLogger.debug(`${LOG_PREFIX} Solana commitment met`, { signature, network, confirmationStatus: statusRow.confirmationStatus, commitment, }) return true } } } catch (error) { sdkLogger.warn(`${LOG_PREFIX} Solana poll transient error`, { signature, network, error: error instanceof Error ? error.message : String(error), }) } const remaining = deadline - Date.now() if (remaining <= 0) break await sleep(Math.min(pollIntervalMs, Math.max(0, remaining))) } sdkLogger.warn( `${LOG_PREFIX} timeout after ${timeoutMs}ms waiting for Solana confirmation on ${signature} (${network}). Returning false.`, ) return false }