In the WeatherNft contract, the WeatherNft::requestMintWeatherNFT
function performs two key operations in this order:
Price Increment: increases s_currentMintPrice
by s_stepIncreasePerMint
NFT Minting: after validating that the sent value covers the new price, issues the NFT
Although this design is atomic, it exposes a front-running window: an attacker can observe the victim’s pending transaction in the mempool, inject their own with higher gas price to execute first, capture the mint at the “old” price, and force the victim to pay the now-increased price.
Legitimate user (victim) calls WeatherNft::requestMintWeatherNFT
with {value: P}
, expecting to pay price P and receive an NFT
Their transaction remains pending in the mempool.
Attacker monitors the mempool and spots the victim’s tx with value = P.
Submits their own WeatherNft::requestMintWeatherNFT
{value: P} with a higher gas price, guaranteeing their call is mined first.
Financial
Attacker acquires mint at the “cheap” price and may resell or manipulate downstream pricing.
Victim incurs unexpected extra cost or an aborted transaction.
Trust & Reputation
Users lose confidence when mints consistently fail or cost more.
Negative perception of protocol integrity.
Availability
Under high demand, repeated exploits can escalate mint costs, deterring legitimate users.
Victim Submits transaction.
Victim calls:
Tx_Victim: WeatherNft::requestMintWeatherNFT
{value: P, gasPrice: 50 gwei}
It remains pending in the mempool.
Attacker Detects It.
Uses a mempool watcher to spot Tx_Victim.
Attacker Front-Runs
Sends a transaction with higher gas price.
Tx_Attacker: WeatherNft::requestMintWeatherNFT
{value: P, gasPrice: 100 gwei}
Miners include it before Tx_Victim.
Attacker’s transaction Executes
s_currentMintPrice
goes from P to P + s_stepIncreasePerMint
.
The contract still reads the old price P for the payment check and mints the NFT to the attacker.
State after execution:
s_currentMintPrice = P + s_stepIncreasePerMint
Attacker owns tokenId N.
Victim’s transaction fails.
When Tx_Victim executes, it sees s_currentMintPrice = P + s_stepIncreasePerMint
but msg.value = P
, so it reverts with “Insufficient payment”.
Consequences
Victim must resend a transaction paying P + Δ to mint their NFT.
Consider implementing one of the following front-running mitigation strategies:
Commit–Reveal via Commit Hash
Introduce a two-phase process where users first submit a hashed commitment of their mint intent (including a secret salt and block reference).Only after the commitment is recorded do they reveal the secret and mint at the price snapshot. This ensures the intended mint parameters remain hidden in the mempool until it’s too late to front-run.
Off-Chain Vouchers with Chainlink Functions
Use a Chainlink Functions job to calculate and sign each user’s mint details (user address, price, a one-time nonce) off-chain. The signed voucher is submitted on-chain when minting; since the price and nonce are only revealed in that final transaction, observers cannot frontrun based on mempool data.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.