Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const BuilderDepositsTable = (props: IBuilderDepositsTableProps): React.ReactEle
}, [dataSourceKey, blsReady]);

// Compute the predeploy queue fee (shared across all rows).
// Unlike EIP-7002/7251, the EIP-8282 contract charges fees per write path: the fee
// numerator is the excess (slot 0) plus the requests already added in the current
// block (slot 1) beyond TARGET_PER_BLOCK, so the fee rises within a block.
const targetPerBlock = 8n; // TARGET_PER_BLOCK of the builder deposit contract
let queueLength = 0n;
let isPreFork = false;
let requiredFee = 0n;
Expand All @@ -73,12 +77,16 @@ const BuilderDepositsTable = (props: IBuilderDepositsTableProps): React.ReactEle
if (queueLength === 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn) {
isPreFork = true;
} else {
requiredFee = getRequiredFee(queueLength);
let feeNumerator = queueLength;
if (queueData.blockCount > targetPerBlock) {
feeNumerator += queueData.blockCount - targetPerBlock;
}
requiredFee = getRequiredFee(feeNumerator);
if (addExtraFee && cachedLogData) {
for (let block in cachedLogData.logCount) avgRequestPerBlock += cachedLogData.logCount[block];
avgRequestPerBlock /= logLookbackRange;
let extra = avgRequestPerBlock < 2 ? 3 : avgRequestPerBlock + 1;
requestFee = getRequiredFee(queueLength + BigInt(Math.ceil(extra)));
requestFee = getRequiredFee(feeNumerator + BigInt(Math.ceil(extra)));
} else {
requestFee = requiredFee;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ const BuilderExitReview = (props: IBuilderExitReviewProps) => {
if (queueLength === 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn) {
isPreFork = true;
} else {
requiredFee = getRequiredFee(queueLength);
// Unlike EIP-7002/7251, the EIP-8282 contract charges fees per write path: the fee
// numerator is the excess (slot 0) plus the requests already added in the current
// block (slot 1) beyond TARGET_PER_BLOCK (2 for the builder exit contract).
let feeNumerator = queueLength;
if (queueData.blockCount > 2n) {
feeNumerator += queueData.blockCount - 2n;
}
requiredFee = getRequiredFee(feeNumerator);

if (addExtraFee && cachedLogData) {
for (let block in cachedLogData.logCount) {
Expand All @@ -56,7 +63,7 @@ const BuilderExitReview = (props: IBuilderExitReviewProps) => {
extraFeeForRequest++;
}

requestFee = getRequiredFee(queueLength + BigInt(Math.ceil(extraFeeForRequest)));
requestFee = getRequiredFee(feeNumerator + BigInt(Math.ceil(extraFeeForRequest)));
} else {
requestFee = requiredFee;
}
Expand Down
30 changes: 26 additions & 4 deletions ui-package/src/hooks/useQueueDataCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useStorageAt, useBlockNumber, usePublicClient } from 'wagmi';

interface QueueData {
queueLength: bigint;
blockCount: bigint;
lastFetch: number;
isLoading: boolean;
error: Error | null;
Expand Down Expand Up @@ -47,7 +48,16 @@ export const useQueueDataCache = (contractAddress: string, chainId?: number) =>

const storageCall = useStorageAt({
address: contractAddress as `0x${string}`,
slot: "0x00",
slot: "0x00", // excess requests (fee numerator base)
chainId,
query: {
enabled: false, // We'll manually control when to fetch
}
});

const countStorageCall = useStorageAt({
address: contractAddress as `0x${string}`,
slot: "0x01", // requests added in the current block
chainId,
query: {
enabled: false, // We'll manually control when to fetch
Expand Down Expand Up @@ -88,12 +98,22 @@ export const useQueueDataCache = (contractAddress: string, chainId?: number) =>
fetchingContracts.add(cacheKey);

try {
const result = await storageCall.refetch();

const [result, countResult] = await Promise.all([
storageCall.refetch(),
countStorageCall.refetch(),
]);

if (result.data) {
// refetch() resolves even on query errors; a missing slot-1 value must not
// silently degrade to 0n, as that would understate the quoted fee
if (countResult.data == null) {
throw countResult.error ?? new Error('Failed to read request count (slot 0x01)');
}
const queueLength = BigInt(result.data as string);
const blockCount = BigInt(countResult.data as string);
const queueData: QueueData = {
queueLength,
blockCount,
lastFetch: Date.now(),
isLoading: false,
error: null,
Expand All @@ -110,6 +130,7 @@ export const useQueueDataCache = (contractAddress: string, chainId?: number) =>
} catch (error) {
const queueData: QueueData = {
queueLength: 0n,
blockCount: 0n,
lastFetch: Date.now(),
isLoading: false,
error: error as Error,
Expand All @@ -125,7 +146,7 @@ export const useQueueDataCache = (contractAddress: string, chainId?: number) =>
} finally {
fetchingContracts.delete(cacheKey);
}
}, [cacheKey, storageCall]);
}, [cacheKey, storageCall, countStorageCall]);

const fetchLogData = useCallback(async () => {
if (!client || !blockNumber.data) return;
Expand Down Expand Up @@ -205,6 +226,7 @@ export const useQueueDataCache = (contractAddress: string, chainId?: number) =>
if (fetchingContracts.has(cacheKey)) {
return {
queueLength: 0n,
blockCount: 0n,
lastFetch: 0,
isLoading: true,
error: null,
Expand Down