Case study · Legends of Hearts
Legends of Hearts arrived as a zip file that could not mint. Exaflair audited it, built the quests web app and the admin panel from scratch, and shipped it on Monad and Base mainnet.
The codebase came over as an archive and a handoff document: a stylized multiplayer web3 action-adventure game with a reward layer that looked finished and could not issue a token. Exaflair audited it, then built the quests web app players use and the admin panel behind it from scratch, developed and deployed the contracts, and shipped to Monad and Base mainnet — redesigning the interface and repairing the inherited contract layer on the way. The mechanism at the centre of it is a signature-gated ERC-721 mint: players earn progress off-chain, a backend key signs a one-time claim, and the contract verifies that signature — with five checks in Solidity — before it will mint anything.
- SubjectThe inherited quest and reward platform behind the Legends of Hearts game
- MeasureTwo products built from scratch — the quests web app and the admin panel — contracts developed and deployed to Monad and Base mainnet, and a security audit that catalogued 46 issues with CVSS v3.1 severities and fixed 42 of them, behind a signature-gated ERC-721 mint that enforces 5 checks in Solidity before it will issue a token
- BaselineThe build as handed over could not complete a mint: outdated ABIs, a broken signature setup, no value sent on the payable mint call, and a hardcoded network and mint fee — so the quests web app and the admin panel were built from scratch, and the contracts were developed and deployed to both mainnets
Proof, before you scroll
Exaflair developed the contracts and deployed them to Monad mainnet and to Base mainnet. The Base pair is the one linked above, and every contract value quoted as text on this page is read from the deployed source and from BaseScan rather than off a screen.
Quest-to-mint lifecycle · selected step
- Earn XP
- Authorize
- Submit
- Verify
- Mint
- Close
Pick a step in the explorer under “What we built”; the table in Result follows it.
Engagement
- Client
Legends of HeartsA stylized multiplayer web3 action-adventure game, made by the studio Angscope.- Sector
- Web3 & blockchainRecorded as the service category on Exaflair's portfolio sheet.
- Exaflair's role
- Built from scratch, audited, deployedThe quests web app built from scratch, the admin panel built from scratch, the security audit, contracts development and deployment, and deployment to Monad and Base mainnet — around an inherited codebase that could not mint, whose contract layer was repaired and whose interface was redesigned on the way.
- Engagement
- OngoingOne piece of work rather than a set of tickets: two products built from scratch, the audit, contracts development and deployment, and both mainnet deployments — with further work committed.
- Networks
- Monad and Base mainnetExaflair developed the contracts and deployed them to both mainnets: Monad mainnet, after Monad testnet, and Base mainnet. It is the Base pair that is linked and read from on this page.
- Stack
In the client's words
Legends of Hearts
“Excellent developers with outstanding communication. We couldn't ask for more. They're expert full-stack programmers who took the entire project to the next level with their proactive recommendations and advice. Totally recommended.”
Supplied by Exaflair.
Context
A reward layer that looked finished, and could not issue a token.
In the client's own words, Legends of Hearts is “a stylized multiplayer Web3 action-adventure game where players race through obstacle-filled levels, convert emotions, liberate hearts, defeat the villain Sharshoor, and share victories with loved ones via NFT gifts on social media.” The heroes are Delmo and Dala, carrying purity weapons against Sharshoor's dark alchemy, and the world is Candy Valley.
That last clause is where the engineering starts. A gift you can share is a token someone actually owns, on a chain, in their own wallet — and a game cannot simply mint one whenever a player asks, or the supply is whatever the studio is willing to give away. The quest layer that was meant to do this arrived at Exaflair as a codebase in an archive with a handoff document: an app that presented a full quest board and could not carry a player through to a token.
What the archive settled was that the quest layer had to be built rather than patched. Exaflair audited what was there, built the quests web app players use and the admin panel the client did not have from scratch, developed the contracts and deployed them to Monad and Base mainnet — redesigning the interface and repairing the inherited contract layer along the way.
Angscope, the studio behind the game, makes immersive, story-driven games with family-friendly gameplay.
-

The run. Score, lives, heart coins and the bell in the heart tree — the game the reward layer attaches to. -

The levels. Platform hazards, a sleeping cat and a portal arch — the “obstacle-filled levels” of the client's description. -

The in-game reward. Gold heart coins collected between plank platforms — the earned thing the token layer sits on top of. -

The weapons. A published concept sheet, not a game screen: seven octopus-themed weapons, each labelled. -

The cast. A published line-up, not a game screen: the archer with a gold heart coin, the girl with a horn, and a squid-headed figure in a white coat. -

The props. A published asset sheet: the heart tree with its bell and the portal doorway among seven level props.
These six are art the client published on legendsofhearts.com. They are the game, not the product — the product screens on this page are labelled as such.
Partner brands shown on the client's site
LootGO
Pixel Realm
Merso
Mind Agents
Constraint
The reward could not complete, and the reason was in the contract layer.
The shape of the mechanism was right: progress is earned off-chain where the game lives, and proven on-chain in a single transaction, because a contract that cannot see a quest has to be handed a proof it can check for itself. What the audit found was that the bridge carrying that proof was broken at both ends, and that the backend underneath it was carrying known vulnerabilities — which is why the quests web app and the admin panel were built from scratch and the contracts were developed and deployed afresh to Monad and Base mainnet.
- The mint call was malformedOutdated ABIs, and no value sent on a payable mint. The transaction could not succeed even for a player who had genuinely earned it.
- The signature scheme did not holdNonce and signature verification were broken — the single thing the whole off-chain gate rests on, and the one place a replayed claim would break the cap.
- The network and the fee were fixed in codeBoth were hardcoded, so neither the chain nor the mint fee could move without a redeploy.
- Twelve high-severity backend dependenciesCarried in with the inherited codebase, before a line of new work was written.
What we built
A signature is the bridge: earned off-chain, verified in Solidity, minted in the same call.
Exaflair built the quests web app players use and the admin panel behind it from scratch, developed and deployed the contracts, and shipped to Monad and Base mainnet. The inherited archive supplied the audit target and the shape of the mechanism; everything a player or an operator touches today is Exaflair's build. This is what shipped.
The quests web app, built from scratch. The front end players actually use: wallet gating, the eighteen-mission board across five categories, the XP scoring behind the Love Score, the remaining-supply counter and the tiered heart badges.
The admin panel, built from scratch. Password authentication and refresh tokens, task creation, and filtered exports — the operations side the client did not have.
The security audit. Forty-six issues catalogued and scored with CVSS v3.1 severities, forty-two of them fixed and twelve high-severity backend dependencies cleared. The contract-layer findings fed the client's own audit.
Contracts development and deployment. The signature-gated ERC-721 pair in Solidity 0.8.27 on OpenZeppelin and ECDSA, developed and deployed — including the repairs the audit turned up in the inherited contract layer: nonce and signature verification corrected, the payable mint call carrying its fee, and the network and fee no longer hardcoded.
Deployment to Monad and Base mainnet. Both networks, as mainnet deployments: Monad mainnet after Monad testnet, and the Base mainnet pair that is linked and read from on this page.
Quiz and evidence tasks. Multiple-choice quizzes with answer validation, and evidence tasks that are reviewed by hand before they count.
Discord roles. Role assignment and membership checks wired to the outcome of a claim.
The interface redesign. The from-scratch app was drawn from the client's own Illustrator mockup and made safe across aspect ratios, with a disclaimer and consent checkbox on the mint.
Staging, deployment and recovery. A staging environment stood up after untangling a frontend-to-backend route mismatch and its nginx configuration, the client's environments restored after they were broken on their side, and the mainnet deployments walked through with the client's own developer.
The quest backend holds a signer key. When a player has earned a mint, it signs an ECDSA payload that hashes the contract address, the chain id, the recipient, the current token counter, a timestamp and a nonce. The player's own wallet then calls safeMint with that signature and the fee, and the contract re-derives the same hash and checks who signed it. Step through the whole lifecycle below — the evidence card, the step description and the table in Result all follow your selection.

In the client's own words on that app: players complete simple tasks to earn XP, called a Love Score; every hundred XP earns an attempt to mint, and a successful mint unlocks a Discord role. This is the quests web app Exaflair built from scratch, on the rewards backend behind it.
App screens on this page are imagery of the product, and the figures inside them are the app's own live counters. Contract values quoted as text on this page are read from the deployed contract source and from the block explorer, never off a screen.
Quest-to-mint lifecycle explorer
Use ← → or click a step.Every value in this card is read from the deployed contract source, not from a screen.
Verify
Solidity 0.8.27, on OpenZeppelin's ERC721, ERC721Pausable, Ownable and ECDSA. Administrative actions sit behind a 300-second cooldown, and tokenURI is constant across every token: one shared artwork, no per-token traits.
-
ERC-721 · Base mainnet · the reward side
Early Legend NFT
The reward side of a quest attempt: the token a player receives when the signed claim verifies.
0xF16e3c2c7E49A0c7610Fb42637f5229181eC64df273tokens minted46holders -
ERC-721 · Base mainnet · the consolation side
Try Again NFT
The consolation side of the same attempt, on the same mechanism — a player walks away with a token either way.
0xC55d7766ABA9e3147478Bb3DE6e3f5f36F4c72EE229tokens minted106holders
The Base mainnet pair Exaflair developed and deployed, alongside the same mechanism on Monad mainnet. Mint and holder counts read from BaseScan.
What we did not build
Boundaries, so the credit lands where it belongs.
Not the game art. The characters, levels, concept sheets and every piece of art on this page are the client's, and remain theirs.
Not the game engine. The 2.5D game itself is the studio's build; Exaflair's work is the quests web app and reward layer around it, built from scratch.
Not the inherited archive. The codebase came over as an archive with a handoff document, and that is what the security audit was run against. The quests web app and the admin panel that shipped were built from scratch, and the contracts were developed and deployed by Exaflair to Monad and Base mainnet.
Not the tokenomics. What the token is worth and how the drop is shaped were the client's calls; Exaflair built and deployed the mechanism that enforces them.
No new chain and no bespoke token standard. The work sits on OpenZeppelin's ERC-721, which is why anyone can read the deployed contract on a block explorer today.
No per-token art or trait system:
tokenURIreturns the same metadata for every token, by design.No claim about players, sales, revenue or secondary trading is made anywhere on this page. The mint and holder counts are the only distribution figures, and they are read from the chain.
Result
What the audit found, and what can be read off the chain.
- 46issues catalogued in the security auditEach scored with a CVSS v3.1 severity
- 42of those issues fixedThe contract-layer findings also fed the client's own audit
- 12high-severity backend dependencies clearedCarried in with the inherited codebase
- 5checks the contract makes per mintFee, cap, signature age, nonce, recovered signer
- 273Early Legend NFTs mintedTo 46 holders, read from BaseScan
- 229Try Again NFTs mintedTo 106 holders on the paired contract
- 7,000maximum supply, enforced in the contractA check inside the mint, not a policy document
- 0.00044ETHmint fee, exactNot a minimum: an exact-amount check, forwarded straight to the owner
The first four tiles are Exaflair's own audit record, from the security audit that ran alongside building the quests web app and the admin panel from scratch. The last four are read from the contracts Exaflair developed and deployed — the Base mainnet pair, deployed as the same mechanism went to Monad mainnet.
Step by step: where it runs, and what the contract enforces
The row for the step you picked in the explorer is highlighted, and clicking a row selects that step. “Where it runs” is the honest split: half of this lifecycle never touches the chain, which is exactly why the signature exists.
| Step | Where it runs | What the contract enforces |
|---|---|---|
| Earn XP | Off-chain, in the quest platform. | Nothing. There is no XP, task or quest state in either contract; the gate is entirely off-chain. |
| Authorize | Off-chain, with the backend's signer key. | Nothing yet — but the payload it signs is what the contract will re-derive: contract address, chain id, recipient, token counter, timestamp, nonce.ECDSA |
| Submit | The player's own wallet, on-chain. | The call arrives with everything the decision needs and the exact fee attached.safeMint(to, signature, nonce, timestamp) |
| Verify | On-chain, inside the mint call. | Five checks: exact fee, under the 7,000 cap, signature younger than one hour, nonce unused for that recipient, recovered signer matches. Any one failing reverts the call. |
| Mint | On-chain, in the same transaction. | Nonce marked used, counter incremented, sequential token id minted, fee forwarded to the owner so no ETH accumulates.NFTMinted |
| Close | On-chain, owner only. | Pausing blocks minting and nothing else: every token already issued stays transferable. Admin actions wait out a 300-second cooldown.MintFeeUpdated · SignerChanged · CoolDownSet |
Selected in the explorer: Verify —
Closing quote
“They took the entire project to the next level with their proactive recommendations and advice.”
Ahmed Y. Alansari, Creative Director, Legends of Hearts.
What's next