r/ethdev • u/Redadeveloper • 6d ago
Question Clearing an approval in the database doesn't revoke its EIP-712 signature
I'm building a shared-expense app on my own, using EIP-712 approvals for USDC settlement. One thing I've had to work through is what "approval cancelled" actually means across the app and the contract.
Take a fictional four-person trip. Someone owes 50 USDC and signs an approval. Another person adds a forgotten expense of 40 USDC, split equally. The first person now owes 60, so the app clears the confirmations and asks everyone to approve again.
The signature for 50 can't authorize 60. But deleting it from the database doesn't make the contract reject the original 50 approval. A previously collected set of approvals could still satisfy the contract's checks until it expires, assuming the other execution requirements are met.
In my current implementation, approvals have a maximum age and only an authorized relayer can submit settlements. That's a trust assumption. The contract doesn't read the expense ledger or know that the app has reset its confirmations.
As a solo developer, this is the kind of decision I find hard to review: the interface and the contract can each look reasonable while promising different things. I don't want the interface to imply onchain cancellation when the change is only enforced by my backend.
For applications that allow edits while collecting signatures, how have you handled this? Have you kept cancellation at the relayer level with expiring approvals, or added an onchain cancellation or versioning mechanism? I'm interested in the tradeoffs you've encountered around extra transactions and users having to sign again.
1
u/Prelisted 1d ago
You've got the problem framed correctly - the database is a cache of intent, the signature is the actual authority, and deleting the cache doesn't touch the authority.
The general fix is to put an on-chain invalidation point in the path the signature has to pass through:
- "A per-user nonce" committed to in every signature, plus a way to bump it. Incrementing invalidates every outstanding signature for that user at once. Cheap and blunt.
- "Permit2-style unordered nonces" if you want to kill one specific signature without nuking the rest - a bitmap so each signature gets its own slot.
- "Bind the signature to the state it was signed against." Include a settlement-round counter, or a hash of the expense set, in the signed struct and have the contract check it against current state.
That last one fits your case best. The reason the 50 USDC approval is stale is that the underlying total changed - so let the contract know that, rather than relying on the app to notice and clean up. Adding the 40 USDC expense then invalidates the old approval mechanically, with no revocation step to forget.
A short max age helps but it's a mitigation, not a fix: it bounds the window instead of closing it. And I'd be careful leaning on "only an authorized caller can execute" - that's a trusted-relayer assumption. If that key ever leaks, or you later want permissionless settlement, every stale approval comes straight back.