From f46deb07ee1b65e193e7406900267806634f7263 Mon Sep 17 00:00:00 2001 From: licette32 Date: Sun, 23 Aug 2026 17:59:45 -0300 Subject: [PATCH] fix(evidence): poll tx lookup instead of single-shot getTransaction (#61) With the frontend on Helius, complete_maintenance confirms and the evidence submission fires faster than api.devnet.solana.com indexes the tx, so the function's single getTransaction call raced indexing and returned a false 'not found' 403. Retry the lookup for up to ~6s before concluding failure and soften the final message to tell the user what to do. --- supabase/functions/evidence/index.ts | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/supabase/functions/evidence/index.ts b/supabase/functions/evidence/index.ts index da451dc..e2ff698 100644 --- a/supabase/functions/evidence/index.ts +++ b/supabase/functions/evidence/index.ts @@ -45,6 +45,29 @@ async function rpc(method: string, params: unknown[]): Promise { return body.result } +type ConfirmedTx = { + meta?: { err?: unknown; logMessages?: string[] } + transaction?: { message?: { accountKeys?: unknown; header?: { numRequiredSignatures?: number } } } +} + +// A freshly-confirmed tx can take a few seconds to become readable through +// the RPC the function queries; one lookup is not enough to conclude failure. +const TX_LOOKUP_ATTEMPTS = 5 +const TX_LOOKUP_DELAY_MS = 1500 + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function fetchConfirmedTx(sig: string): Promise { + for (let attempt = 0; attempt < TX_LOOKUP_ATTEMPTS; attempt++) { + const tx = (await rpc('getTransaction', [sig, { encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 }])) as ConfirmedTx | null + if (tx) return tx + if (attempt < TX_LOOKUP_ATTEMPTS - 1) await sleep(TX_LOOKUP_DELAY_MS) + } + return null +} + async function verifyMaintenanceTx(params: { txSignature: string assetPda: string @@ -55,16 +78,15 @@ async function verifyMaintenanceTx(params: { return { ok: false, error: 'Invalid transaction signature' } } - let tx: { - meta?: { err?: unknown; logMessages?: string[] } - transaction?: { message?: { accountKeys?: unknown; header?: { numRequiredSignatures?: number } } } - } + let tx: ConfirmedTx try { - tx = (await rpc('getTransaction', [sig, { encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 }])) as typeof tx + tx = await fetchConfirmedTx(sig) } catch (e) { return { ok: false, error: e instanceof Error ? e.message : 'Transaction lookup failed' } } - if (!tx) return { ok: false, error: 'Transaction not found or not confirmed yet' } + if (!tx) { + return { ok: false, error: 'Transaction is still confirming — wait a few seconds and attach the evidence again' } + } if (tx.meta?.err) return { ok: false, error: 'Transaction failed on-chain' } const logs: string[] = Array.isArray(tx.meta?.logMessages) ? tx.meta.logMessages : []