Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 113 additions & 6 deletions frontend/src/pages/AccountReceive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,98 @@ function useScan() {
return provider.getBlock('latest');
}

function getPonderNetworkName(chainId: number): string | null {
switch (chainId) {
case 1:
return 'mainnet';
case 10:
return 'optimism';
case 137:
return 'polygon';
case 8453:
return 'base';
case 42161:
return 'arbitrumOne';
case 11155111:
return 'sepolia';
default:
return null;
}
}

async function getSubgraphHeadBlockNumber(chainId: number, subgraphUrl: string): Promise<number> {
const headers = { 'Content-Type': 'application/json' };
const ponderNetwork = getPonderNetworkName(chainId);

if (ponderNetwork) {
try {
const ponderResponse = await fetch(subgraphUrl, {
method: 'POST',
headers,
body: JSON.stringify({
query: `{
_meta {
status
}
}`,
}),
});

if (!ponderResponse.ok) throw new Error(`Subgraph head request failed with status ${ponderResponse.status}`);

const ponderPayload = (await ponderResponse.json()) as {
data?: { _meta?: { status?: Record<string, { block?: { number?: number } }> } };
errors?: Array<{ message: string }>;
};

if (ponderPayload.errors?.length) {
throw new Error(ponderPayload.errors.map((error) => error.message).join('; '));
}

const ponderHead = ponderPayload.data?._meta?.status?.[ponderNetwork]?.block?.number;
if (typeof ponderHead === 'number' && Number.isFinite(ponderHead)) {
return ponderHead;
}
} catch {
// Legacy endpoints can reject the Ponder query with different HTTP statuses or GraphQL errors.
}
}

const legacyResponse = await fetch(subgraphUrl, {
method: 'POST',
headers,
body: JSON.stringify({
query: `{
_meta {
block {
number
}
}
}`,
}),
});

if (!legacyResponse.ok) throw new Error(`Legacy subgraph head request failed with status ${legacyResponse.status}`);

const legacyPayload = (await legacyResponse.json()) as {
data?: { _meta?: { block?: { number?: number | string } } };
errors?: Array<{ message: string }>;
};
if (legacyPayload.errors?.length) {
throw new Error(legacyPayload.errors.map((error) => error.message).join('; '));
}

const legacyHead = legacyPayload.data?._meta?.block?.number;
if (typeof legacyHead === 'number' && Number.isFinite(legacyHead)) {
return legacyHead;
}
if (typeof legacyHead === 'string' && legacyHead.length > 0) {
return Number(legacyHead);
}

throw new Error(`Missing subgraph head block for chain ${chainId}`);
}

async function scan() {
// Reset paused state
paused.value = false;
Expand All @@ -409,7 +501,8 @@ function useScan() {
window.logger.debug(
`Scanning for announcements from ${startBlockLocal.value ?? 'undefined'} to ${endBlockLocal.value ?? 'undefined'}`
);
const overrides = { startBlock: startBlockLocal.value, endBlock: endBlockLocal.value };
// Cleared number inputs can be empty strings; treat an empty end block as an unrestricted scan.
const overrides = { startBlock: startBlockLocal.value, endBlock: endBlockLocal.value || undefined };

// Scan for funds
const spendingPubKey = chooseKey(spendingKeyPair.value?.publicKeyHex);
Expand Down Expand Up @@ -482,6 +575,19 @@ function useScan() {
const latestBlock: Block = await getLastBlock(provider.value!);
mostRecentBlockNumber.value = latestBlock.number;
mostRecentBlockTimestamp.value = latestBlock.timestamp;
let nextStartBlock: number | undefined;
if (umbra.value.chainConfig.subgraphUrl) {
try {
nextStartBlock = await getSubgraphHeadBlockNumber(
umbra.value.chainConfig.chainId,
umbra.value.chainConfig.subgraphUrl
);
} catch (error) {
window.logger.warn('Failed to fetch subgraph head block, preserving previous checkpoint', error);
}
} else {
nextStartBlock = latestBlock.number;
}

// Default scan behavior
for await (const announcementsBatch of umbra.value.fetchSomeAnnouncements(
Expand Down Expand Up @@ -533,11 +639,12 @@ function useScan() {
await filterUserAnnouncementsAsync(spendingPubKey, viewingPrivKey, announcementsQueue);
scanStatus.value = 'complete';

// Save the latest block to localStorage for future scans as the start block
setLastFetchedBlock(latestBlock.number);

// Update startBlockLocal with the latest block number
startBlockLocal.value = latestBlock.number;
// Cap the checkpoint at the RPC head too, since announcement fetching may fall back to RPC logs.
if (nextStartBlock !== undefined) {
const checkpoint = Math.min(nextStartBlock, latestBlock.number, overrides.endBlock ?? nextStartBlock);
setLastFetchedBlock(checkpoint);
startBlockLocal.value = checkpoint;
}
}
} catch (e) {
scanStatus.value = 'waiting'; // reset to the default state because we were unable to fetch announcements
Expand Down
Loading