Gating engagement rewards on real product usage — implementation notes

When I first integrated EngagementRewards into celobank-agent (an autonomous DeFi agent on Celo), the reward fired as soon as a user completed GoodDollar identity verification. DeMewlingOak reviewed it and pointed out the flaw: that rewards a GoodDollar-native action, not engagement with my product. The mechanism is meant to reward someone who actually used your dapp and came back.

Fair point, and reworking it turned out to be more interesting than expected. Sharing the notes in case anyone else is wiring this up.

What the gate looks like now

A user can only claim once they’ve done 2+ distinct qualifying on-chain actions through the agent (swap, save, stake, send), and there’s a 2+ day gap between their first and their most recent action — a returning-user signal rather than a burst. Eligibility is tracked in Postgres, and there’s a 180-day cooldown between claims.

Four things that bit me

Self-transfers count as engagement if you let them. Sending funds to your own address is a valid on-chain action and trivially farmable. Excluded explicitly.

One user action can emit several on-chain events. An unstake cycle was registering as multiple qualifying actions, so a single operation could satisfy a 2-action gate on its own. Actions now have to be distinct by type, not by transaction count.

Gating in the UI is not gating. The eligibility check originally lived in the tool layer, so calling the API endpoint directly skipped it entirely. The check has to sit at the point of claim, not at the point of display.

Reverted transactions were being recorded as completed. Worth checking the receipt status rather than the fact that a transaction was broadcast.

Attribution

A subtler one: if your agent lets users interact with external DeFi contracts, any transaction they make through those contracts looks like product usage from the outside. I now tag transactions originated by the agent and only count those, so activity that merely passed through a shared contract doesn’t qualify.

Concurrency

Two simultaneous claim requests could both pass the eligibility check before either wrote its result. Solved with a database advisory lock around the check-and-claim, load-tested deterministically.

All of this is live in production and the code is open source: GitHub - wkalidev/celobank-agent: Autonomous DeFi agent on Celo — 21 MCP tools, Claude Haiku AI, ERC-8004 registered, Self Agent ID #171 verified, SDK @celobank/agent-sdk · GitHub

Two open questions for anyone further along than me:

  1. Is there guidance somewhere on what counts as a valid engagement loop, or is it case-by-case review? I’d rather build to the intent than guess.
  2. My app shows isRegistered: true but isApproved: false — I’ve raised it in the builders group. If there’s a documented review process I’ve missed, happy to be pointed at it.
2 Likes

What happened to good dollar calms rewards

Why did they stopped?

Can someone explain better thanks

The incident affecting GoodDollar on Celo has been contained, and the protocol remains temporarily paused on Celo.

The root cause has been identified. The incident did not result from a vulnerability in the GoodDollar protocol, but it impacted liquidity in the GoodDollar Reserve and other G$ liquidity pools.

Further updates and details will be shared as more information is confirmed.

Thank you for your understanding and support :blue_heart: