<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mililin</title>
    <description>The latest articles on DEV Community by mililin (@mililin_f4f9ec3965934d912).</description>
    <link>https://dev.to/mililin_f4f9ec3965934d912</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166708%2F0731800c-2106-4e65-a1a2-03b6141d1ac6.png</url>
      <title>DEV Community: mililin</title>
      <link>https://dev.to/mililin_f4f9ec3965934d912</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mililin_f4f9ec3965934d912"/>
    <language>en</language>
    <item>
      <title>When the Smart Contract Works, but the Payment Is Wrong: A Collusion Attack in MealForward (Part 1)</title>
      <dc:creator>mililin</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:02:23 +0000</pubDate>
      <link>https://dev.to/mililin_f4f9ec3965934d912/when-the-smart-contract-works-but-the-payment-is-wrong-a-collusion-attack-in-mealforward-part-1-4pj7</link>
      <guid>https://dev.to/mililin_f4f9ec3965934d912/when-the-smart-contract-works-but-the-payment-is-wrong-a-collusion-attack-in-mealforward-part-1-4pj7</guid>
      <description>&lt;p&gt;Recently, I've been working on &lt;a href="https://github.com/3956ray/MealForward" rel="noopener noreferrer"&gt;MealForward&lt;/a&gt;, a community meal voucher project built for the Monad Hackathon.&lt;/p&gt;

&lt;p&gt;The idea is straightforward: donors deposit funds into an on-chain escrow contract, authorized issuers distribute meal vouchers, and recipients redeem those vouchers at participating restaurants using temporary QR codes. Once a restaurant confirms a meal has been delivered, the escrow contract releases the corresponding payment to the merchant.&lt;/p&gt;

&lt;p&gt;While reviewing the smart contracts, we came across an interesting question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if a smart contract executes exactly as designed, but the money still ends up being paid for something that never happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question led us beyond Solidity-level security and into a broader discussion about collusion attacks, on-chain/off-chain consistency, EIP-712 authorization, delayed settlement, and trust assumptions.&lt;/p&gt;

&lt;p&gt;Here's how our reasoning evolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. It Started With Two Solidity Functions
&lt;/h2&gt;

&lt;p&gt;We began by reviewing two functions in MealForward's testnet escrow contract: &lt;code&gt;report()&lt;/code&gt; and &lt;code&gt;settle()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here are simplified excerpts from the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Operator reports meal delivery
function report(
    bytes32 operationId,
    bytes32 voucherId,
    bytes32 lockId
) external onlyRole(OPERATOR_ROLE) nonReentrant {
    Voucher storage v = vouchers[voucherId];

    if (v.status != 2 || v.lockId != lockId)
        revert InvalidVoucherState();

    v.status = 3;

    // Move funds from R (issued) to H (pending settlement)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Settler releases payment to the merchant
function settle(
    bytes32 operationId,
    bytes32 voucherId
) external onlyRole(SETTLER_ROLE) nonReentrant {
    // Validate voucher state and update accounting

    (bool paid,) =
        merchant.call{value: priceWei}("");

    if (!paid) revert PaymentFailed();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;These are simplified excerpts. Operation replay checks, pause conditions, and parts of the accounting logic are omitted for clarity. The full implementation is available in &lt;a href="https://github.com/3956ray/MealForward/blob/codex/cp23-private-link/contracts/MealForwardTestnet.sol" rel="noopener noreferrer"&gt;MealForwardTestnet.sol&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At first glance, the access control looks reasonable.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OPERATOR_ROLE&lt;/code&gt; can lock vouchers and report meal delivery, while the &lt;code&gt;SETTLER_ROLE&lt;/code&gt; can release funds for vouchers that have reached the appropriate state.&lt;/p&gt;

&lt;p&gt;The contract also restricts payments to a predefined merchant address and prevents the same voucher from being successfully settled multiple times.&lt;/p&gt;

&lt;p&gt;But there is a fundamental limitation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An authorized Operator can submit a valid delivery report without proving that a meal was actually delivered.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The contract can verify the caller's permissions and whether a state transition is allowed.&lt;/p&gt;

&lt;p&gt;It cannot directly observe what happens inside a restaurant.&lt;/p&gt;

&lt;p&gt;That led us to our first attack scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What If the Operator and Merchant Collude?
&lt;/h2&gt;

&lt;p&gt;Suppose an attacker compromises the Operator's private key and cooperates with a dishonest restaurant owner.&lt;/p&gt;

&lt;p&gt;The attacker cannot directly withdraw funds using only &lt;code&gt;OPERATOR_ROLE&lt;/code&gt;, because &lt;code&gt;settle()&lt;/code&gt; requires a separate role.&lt;/p&gt;

&lt;p&gt;However, the attacker can potentially create fraudulent redemption records using legitimately issued, unused vouchers.&lt;/p&gt;

&lt;p&gt;The attack could work like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The attacker uses the compromised Operator key to call &lt;code&gt;lock()&lt;/code&gt; on a valid, unredeemed voucher.&lt;/li&gt;
&lt;li&gt;The Operator calls &lt;code&gt;report()&lt;/code&gt;, falsely declaring that a meal has been delivered.&lt;/li&gt;
&lt;li&gt;The colluding merchant, assuming they have settlement authority, executes &lt;code&gt;settle()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The escrow contract transfers the corresponding voucher value to the merchant's predefined wallet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No meal was actually delivered.&lt;/p&gt;

&lt;p&gt;Yet every transaction could satisfy the contract's access-control and state-transition requirements.&lt;/p&gt;

&lt;p&gt;To be precise, the attacker cannot create unlimited funds. The attack is constrained by existing vouchers, escrow balances, and the roles available to the colluding parties.&lt;/p&gt;

&lt;p&gt;But it can still cause existing donor funds to be released for fraudulent claims.&lt;/p&gt;

&lt;p&gt;This is not necessarily a traditional Solidity vulnerability such as reentrancy or an arithmetic error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a business-logic and trust-model vulnerability: authorized parties can collude to produce a financially incorrect outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that raises a bigger question:&lt;/p&gt;

&lt;p&gt;How do we protect a DApp when the contracts are behaving correctly, but the participants feeding information into those contracts may be dishonest?&lt;/p&gt;

&lt;h2&gt;
  
  
  3. First Approach: On-Chain and Off-Chain Reconciliation
&lt;/h2&gt;

&lt;p&gt;Before addressing collusion directly, we considered another practical problem.&lt;/p&gt;

&lt;p&gt;Imagine a restaurant has already served a meal and the backend database records the voucher as delivered.&lt;/p&gt;

&lt;p&gt;However, the on-chain &lt;code&gt;lock()&lt;/code&gt; transaction fails because of a network issue or because the contract has been paused.&lt;/p&gt;

&lt;p&gt;Now the two systems disagree:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Off-chain database&lt;/th&gt;
&lt;th&gt;On-chain contract&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Voucher marked as delivered&lt;/td&gt;
&lt;td&gt;Voucher remains issued&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meal has already been served&lt;/td&gt;
&lt;td&gt;Lock was not completed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voucher must not be reused&lt;/td&gt;
&lt;td&gt;Contract state may still allow locking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a classic on-chain/off-chain consistency problem.&lt;/p&gt;

&lt;p&gt;Our first idea was to introduce &lt;strong&gt;Reconciliation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An Indexer can observe on-chain transactions, event logs, and contract state, then compare them with off-chain business records such as vouchers, delivery confirmations, and operation histories.&lt;/p&gt;

&lt;p&gt;When an inconsistency is detected, the system can flag it for investigation and recovery.&lt;/p&gt;

&lt;p&gt;However, recovery is not as simple as repeatedly submitting transactions.&lt;/p&gt;

&lt;p&gt;If a transaction is in an &lt;code&gt;UNKNOWN&lt;/code&gt; state, we should investigate the original transaction hash, nonce, receipt, and signing records before creating another attempt.&lt;/p&gt;

&lt;p&gt;If the transaction has definitively reverted, we need to understand why it failed before deciding whether a new attempt is safe.&lt;/p&gt;

&lt;p&gt;A physical meal cannot be reversed simply because an on-chain transaction failed.&lt;/p&gt;

&lt;p&gt;This means we need to distinguish between business state and blockchain transaction state rather than treating them as identical.&lt;/p&gt;

&lt;p&gt;But reconciliation revealed another limitation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the Operator and Merchant fabricate matching on-chain and off-chain records?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database says the meal was delivered.&lt;/p&gt;

&lt;p&gt;The blockchain says the voucher was reported.&lt;/p&gt;

&lt;p&gt;Everything matches.&lt;/p&gt;

&lt;p&gt;But no meal was served.&lt;/p&gt;

&lt;p&gt;We realized that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data consistency does not imply factual correctness.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reconciliation can detect discrepancies between systems, but it cannot independently prove that the underlying real-world event occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Second Approach: Delayed Settlement
&lt;/h2&gt;

&lt;p&gt;If monitoring can detect some suspicious activities, perhaps we can give the system more time to react before releasing funds.&lt;/p&gt;

&lt;p&gt;This led us to &lt;strong&gt;Delayed Settlement&lt;/strong&gt;, also known as a settlement challenge period.&lt;/p&gt;

&lt;p&gt;Instead of immediately transferring funds after &lt;code&gt;report()&lt;/code&gt;, the contract could enforce a waiting period.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lock
  ↓
Report
  ↓
30-minute Settlement Delay
  ↓
Settle
  ↓
Merchant receives payment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During this period, the funds remain in escrow.&lt;/p&gt;

&lt;p&gt;An independent monitoring service could compare business records with on-chain activity and look for unusual redemption patterns.&lt;/p&gt;

&lt;p&gt;If suspicious activity is detected, an authorized Guardian could mark a voucher as disputed or pause future settlements.&lt;/p&gt;

&lt;p&gt;Two conditions are important here.&lt;/p&gt;

&lt;p&gt;First, the waiting period must be enforced by the smart contract itself.&lt;/p&gt;

&lt;p&gt;If it exists only in backend code, a compromised privileged actor may be able to bypass it by interacting directly with the contract.&lt;/p&gt;

&lt;p&gt;Second, monitoring and emergency intervention should not depend on the same compromised Operator key.&lt;/p&gt;

&lt;p&gt;Otherwise, an attacker who controls the Operator might also be able to disable monitoring or interfere with incident response.&lt;/p&gt;

&lt;p&gt;A possible settlement check would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    block.timestamp &amp;gt;=
    reportedAt[voucherId] + SETTLEMENT_DELAY,
    "Settlement not ready"
);

require(
    !disputed[voucherId],
    "Voucher disputed"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, this would require additional contract state and carefully designed dispute permissions.&lt;/p&gt;

&lt;p&gt;But even with a 30-minute delay, a dishonest Merchant and Operator could fabricate consistent records that the Monitor considers legitimate.&lt;/p&gt;

&lt;p&gt;The Monitor might never detect anything suspicious.&lt;/p&gt;

&lt;p&gt;So we reached another conclusion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delayed settlement provides time for detection and intervention. It does not prove physical delivery or eliminate collusion.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We needed a mechanism that prevented a compromised Operator from manufacturing authorized voucher claims in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Third Approach: Bring the Recipient's Voucher Into the Authorization Process
&lt;/h2&gt;

&lt;p&gt;Until this point, the system relied heavily on the Operator's authority.&lt;/p&gt;

&lt;p&gt;What if the Operator's private key alone were no longer sufficient to redeem a voucher?&lt;/p&gt;

&lt;p&gt;Could we require proof that a valid recipient credential actually participated in the redemption?&lt;/p&gt;

&lt;p&gt;This led us to the idea of a &lt;strong&gt;Claim Permit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The central idea is simple:&lt;/p&gt;

&lt;p&gt;An Operator should not be able to lock a voucher merely because it holds &lt;code&gt;OPERATOR_ROLE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It must also provide a valid, one-time authorization issued for a specific voucher and merchant.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ClaimPermit {
    bytes32 voucherId;
    address merchant;
    bytes32 claimId;
    uint256 nonce;
    uint256 deadline;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The redemption flow could work as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The recipient presents a temporary QR code at the restaurant.&lt;/li&gt;
&lt;li&gt;A Redemption Authority validates the QR code, merchant identity, and voucher status.&lt;/li&gt;
&lt;li&gt;The backend atomically consumes or reserves the one-time presentation credential.&lt;/li&gt;
&lt;li&gt;The Redemption Authority signs a Claim Permit for that specific voucher and merchant.&lt;/li&gt;
&lt;li&gt;The Operator submits the signed Permit to the escrow contract through &lt;code&gt;lock()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The contract verifies the signature, merchant binding, nonce, expiration time, and voucher state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can implement this authorization using &lt;strong&gt;EIP-712&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is EIP-712?
&lt;/h3&gt;

&lt;p&gt;EIP-712 is an Ethereum standard for hashing and signing structured typed data.&lt;/p&gt;

&lt;p&gt;Instead of signing an opaque message, we can define exactly which fields are being authorized.&lt;/p&gt;

&lt;p&gt;For example, the Claim Permit could be signed under a domain containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MealForwardClaim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chainId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verifyingContract"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESCROW_CONTRACT_ADDRESS"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain helps bind the signature to the intended chain and contract.&lt;/p&gt;

&lt;p&gt;We must still implement replay protection ourselves, such as nonce consumption, voucher-state checks, and expiration validation.&lt;/p&gt;

&lt;p&gt;EIP-712 does not automatically provide those protections.&lt;/p&gt;

&lt;p&gt;It's also important not to confuse our Claim Permit with EIP-2612's &lt;code&gt;permit()&lt;/code&gt; mechanism for ERC-20 token allowances.&lt;/p&gt;

&lt;p&gt;Our Claim Permit is a custom, application-level authorization built using EIP-712 signatures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Does This Help Against a Compromised Operator?
&lt;/h3&gt;

&lt;p&gt;Let's revisit our original attack scenario.&lt;/p&gt;

&lt;p&gt;An attacker compromises the Operator's private key and cooperates with a dishonest merchant.&lt;/p&gt;

&lt;p&gt;Previously, the Operator could attempt to lock any issued voucher that satisfied the on-chain state requirements.&lt;/p&gt;

&lt;p&gt;With the proposed Claim Permit mechanism, the contract would reject &lt;code&gt;lock()&lt;/code&gt; unless a valid authorization from the designated Redemption Authority accompanies the transaction.&lt;/p&gt;

&lt;p&gt;The Operator cannot simply invent that signature.&lt;/p&gt;

&lt;p&gt;Assuming the Redemption Authority remains honest, its signing credentials are secure, and the attacker cannot obtain a valid recipient credential, the attacker cannot fabricate an authorized redemption using only the compromised Operator key.&lt;/p&gt;

&lt;p&gt;This changes the security model.&lt;/p&gt;

&lt;p&gt;We are no longer trusting the Operator alone to decide whether a particular voucher should be redeemed.&lt;/p&gt;

&lt;p&gt;Instead, the contract enforces an additional authorization boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Does a Claim Permit Prove a Meal Was Delivered?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;This is perhaps the most important lesson from the entire discussion.&lt;/p&gt;

&lt;p&gt;Imagine a dishonest restaurant scans a legitimate recipient's QR code and obtains a valid Claim Permit.&lt;/p&gt;

&lt;p&gt;The Operator then successfully locks the voucher.&lt;/p&gt;

&lt;p&gt;But the restaurant never serves the meal.&lt;/p&gt;

&lt;p&gt;Every cryptographic check may pass.&lt;/p&gt;

&lt;p&gt;The Permit proves that the redemption was authorized under the defined rules.&lt;/p&gt;

&lt;p&gt;It does not prove that the merchant fulfilled its real-world obligation.&lt;/p&gt;

&lt;p&gt;We therefore need to distinguish among three concepts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claim Permit:&lt;/strong&gt; Authorization to redeem a specific voucher.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delivery Declaration:&lt;/strong&gt; A statement from the merchant that a meal has been delivered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delivery Evidence:&lt;/strong&gt; Information supporting the claim that delivery actually occurred, potentially including independent recipient confirmation, audits, or dispute records.&lt;/p&gt;

&lt;p&gt;We could introduce an additional confirmation step after delivery, allowing recipients to confirm that they received their meals.&lt;/p&gt;

&lt;p&gt;However, that comes with practical challenges.&lt;/p&gt;

&lt;p&gt;Some recipients may not have smartphones, wallets, or reliable internet access.&lt;/p&gt;

&lt;p&gt;And even a valid recipient confirmation cannot eliminate the possibility that the recipient and restaurant are colluding.&lt;/p&gt;

&lt;p&gt;For MealForward, maintaining accessibility is a major product requirement, so requiring every recipient to have an Ethereum wallet would introduce significant friction.&lt;/p&gt;

&lt;p&gt;The objective is therefore not to make an impossible claim of cryptographically proving every physical meal.&lt;/p&gt;

&lt;p&gt;It is to &lt;strong&gt;reduce the authority of compromised operators, strengthen redemption authorization, introduce independent evidence, and limit the financial impact of fraudulent activity.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: From Contract Correctness to System Security
&lt;/h2&gt;

&lt;p&gt;We started by reviewing two Solidity functions: &lt;code&gt;report()&lt;/code&gt; and &lt;code&gt;settle()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first, the question seemed to be whether the access-control logic was secure.&lt;/p&gt;

&lt;p&gt;But by constructing a collusion attack, we discovered that smart contract correctness is only one part of DApp security.&lt;/p&gt;

&lt;p&gt;Our reasoning evolved through three approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reconciliation:&lt;/strong&gt; Detect inconsistencies between on-chain transactions and off-chain business records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delayed Settlement:&lt;/strong&gt; Provide an enforceable window for monitoring, disputes, and emergency intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EIP-712 Claim Permit:&lt;/strong&gt; Prevent a compromised Operator from creating authorized voucher claims using only its own private key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach addresses a different problem, and none is sufficient on its own.&lt;/p&gt;

&lt;p&gt;For this first phase, we're working under one explicit assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MealForward's core authorization infrastructure is trustworthy, but the Operator's private key may be compromised and the attacker may collude with a merchant.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Under that assumption, a properly enforced Claim Permit can meaningfully reduce the Operator's ability to create fraudulent redemptions.&lt;/p&gt;

&lt;p&gt;These mechanisms are currently part of our security-design exploration, not a claim that the existing MealForward testnet contract has already implemented or passed an audit for them.&lt;/p&gt;

&lt;p&gt;But our discussion eventually led to an even more difficult question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if MealForward itself becomes malicious?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if the Operator, Redemption Authority, and Monitor are all controlled by the same platform administrator?&lt;/p&gt;

&lt;p&gt;Would separate servers and separate private keys still provide meaningful security?&lt;/p&gt;

&lt;p&gt;That's where our next discussion begins: &lt;strong&gt;Trust Domain Separation, independent authorization, and designing a DApp that doesn't require users to blindly trust the platform itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;To be continued in Part 2.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/3956ray/MealForward" rel="noopener noreferrer"&gt;MealForward on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eips.ethereum.org/EIPS/eip-712" rel="noopener noreferrer"&gt;EIP-712: Typed Structured Data Hashing and Signing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eips.ethereum.org/EIPS/eip-2612" rel="noopener noreferrer"&gt;EIP-2612: Permit Extension for EIP-20 Signed Approvals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openzeppelin.com/contracts/5.x/access-control" rel="noopener noreferrer"&gt;OpenZeppelin AccessControl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I'd love to hear your thoughts:&lt;/strong&gt; For DApps that depend on real-world fulfillment, what other practical approaches can reduce Operator–Merchant collusion without requiring every recipient to own a crypto wallet?&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>crypto</category>
      <category>security</category>
      <category>smartcontract</category>
    </item>
    <item>
      <title>ButtonPost: Write once. Publish everywhere.</title>
      <dc:creator>mililin</dc:creator>
      <pubDate>Tue, 06 Oct 2026 18:27:56 +0000</pubDate>
      <link>https://dev.to/mililin_f4f9ec3965934d912/buttonpost-write-once-publish-everywhere-17d6</link>
      <guid>https://dev.to/mililin_f4f9ec3965934d912/buttonpost-write-once-publish-everywhere-17d6</guid>
      <description>&lt;p&gt;ButtonPost — post to multiple platforms with one click.&lt;/p&gt;

&lt;p&gt;Honestly, the reason I built this is pretty simple: manually posting stuff everywhere got old real fast. I just want to share the same content, but having to click through a bunch of different apps every single time is such a pain.&lt;/p&gt;

&lt;p&gt;Right now it works with X, Dev Community, and Xiaohongshu (RED). Down the road, I'm planning to add Douyin, Reddit, Stack Overflow, and a bunch of other platforms.&lt;/p&gt;

&lt;p&gt;The goal is to save time — and hopefully lower the effort it takes to share something at all.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>buildinpublic</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>test post</title>
      <dc:creator>mililin</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:43:41 +0000</pubDate>
      <link>https://dev.to/mililin_f4f9ec3965934d912/test-post-15h3</link>
      <guid>https://dev.to/mililin_f4f9ec3965934d912/test-post-15h3</guid>
      <description>&lt;p&gt;test post&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hello Dev</title>
      <dc:creator>mililin</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:38:59 +0000</pubDate>
      <link>https://dev.to/mililin_f4f9ec3965934d912/hello-dev-4ho5</link>
      <guid>https://dev.to/mililin_f4f9ec3965934d912/hello-dev-4ho5</guid>
      <description>&lt;p&gt;Hello Dev&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
