Hanzo Quest
Gamified engagement and loyalty programs
Build engaging quest systems and loyalty programs. Users complete tasks, earn points, and claim rewards—NFTs, tokens, or real-world perks. Drive engagement and retention with gamification.
Quest Builder
Create quests with on-chain and off-chain tasks. No code required.
Points System
Award points for completing tasks. Leaderboards and tiers included.
Reward Distribution
Distribute NFTs, tokens, or whitelist spots. Merkle drops supported.
Referral Programs
Built-in referral tracking with customizable reward structures.
Get Your API Key
Start building in under 5 minutes
No credit card required. Free tier includes 300M compute units/month.
Trusted by developers building:
Key Capabilities
Everything you need, nothing you don't.
Quest Builder
Create quests with on-chain and off-chain tasks. No code required.
Points System
Award points for completing tasks. Leaderboards and tiers included.
Reward Distribution
Distribute NFTs, tokens, or whitelist spots. Merkle drops supported.
Referral Programs
Built-in referral tracking with customizable reward structures.
Achievement Badges
Soulbound tokens (SBTs) for achievements that users can showcase.
Analytics
Track quest completion, user retention, and reward redemption.
Notifications
Push notifications for new quests, rewards, and milestones.
Gamification Widgets
Embeddable widgets for progress bars, leaderboards, and rewards.
Simple to Integrate
Get started with just a few lines of code. SDKs for every language.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/// @title QuestRewards - Gamified engagement with on-chain rewards
/// @notice Quest completion, points tracking, and reward distribution
contract QuestRewards is ERC721, Ownable {
IERC20 public rewardToken;
// Quest campaign struct
struct Campaign {
string name;
uint256 startTime;
uint256 endTime;
bytes32 merkleRoot;
uint256 totalRewards;
bool active;
}
// User progress tracking
struct UserProgress {
uint256 points;
uint256 questsCompleted;
uint256 lastClaimTime;
bool hasClaimed;
}
mapping(uint256 => Campaign) public campaigns;
mapping(uint256 => mapping(address => UserProgress)) public userProgress;
mapping(uint256 => mapping(address => bool)) public questCompleted;
uint256 public campaignCount;
uint256 public badgeTokenId;
event CampaignCreated(uint256 indexed campaignId, string name, uint256 startTime, uint256 endTime);
event QuestCompleted(uint256 indexed campaignId, address indexed user, uint256 questId, uint256 points);
event RewardsClaimed(uint256 indexed campaignId, address indexed user, uint256 amount);
event BadgeMinted(uint256 indexed campaignId, address indexed user, uint256 tokenId);
constructor(
address _rewardToken
) ERC721("Quest Badge", "QBADGE") Ownable(msg.sender) {
rewardToken = IERC20(_rewardToken);
}
// Create a new quest campaign
function createCampaign(
string calldata name,
uint256 startTime,
uint256 endTime,
bytes32 merkleRoot,
uint256 totalRewards
) external onlyOwner returns (uint256 campaignId) {
campaignId = campaignCount++;
campaigns[campaignId] = Campaign({
name: name,
startTime: startTime,
endTime: endTime,
merkleRoot: merkleRoot,
totalRewards: totalRewards,
active: true
});
emit CampaignCreated(campaignId, name, startTime, endTime);
}
// Complete a quest (called by backend with proof)
function completeQuest(
uint256 campaignId,
uint256 questId,
uint256 points,
bytes32[] calldata proof
) external {
Campaign storage campaign = campaigns[campaignId];
require(campaign.active, "Campaign not active");
require(block.timestamp >= campaign.startTime, "Campaign not started");
require(block.timestamp <= campaign.endTime, "Campaign ended");
// Verify quest completion proof
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, questId, points));
require(MerkleProof.verify(proof, campaign.merkleRoot, leaf), "Invalid proof");
require(!questCompleted[campaignId][msg.sender], "Quest already completed");
questCompleted[campaignId][msg.sender] = true;
UserProgress storage progress = userProgress[campaignId][msg.sender];
progress.points += points;
progress.questsCompleted++;
emit QuestCompleted(campaignId, msg.sender, questId, points);
}
// Claim token rewards
function claimRewards(
uint256 campaignId,
uint256 amount,
bytes32[] calldata proof
) external {
Campaign storage campaign = campaigns[campaignId];
require(block.timestamp > campaign.endTime, "Campaign not ended");
UserProgress storage progress = userProgress[campaignId][msg.sender];
require(!progress.hasClaimed, "Already claimed");
// Verify reward amount proof
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(MerkleProof.verify(proof, campaign.merkleRoot, leaf), "Invalid proof");
progress.hasClaimed = true;
progress.lastClaimTime = block.timestamp;
rewardToken.transfer(msg.sender, amount);
emit RewardsClaimed(campaignId, msg.sender, amount);
}
// Mint achievement badge (SBT)
function mintBadge(uint256 campaignId, uint256 minPoints) external {
UserProgress storage progress = userProgress[campaignId][msg.sender];
require(progress.points >= minPoints, "Not enough points");
uint256 tokenId = badgeTokenId++;
_safeMint(msg.sender, tokenId);
emit BadgeMinted(campaignId, msg.sender, tokenId);
}
// Soulbound - prevent transfers
function _update(address to, uint256 tokenId, address auth) internal override returns (address) {
address from = _ownerOf(tokenId);
require(from == address(0), "Soulbound: non-transferable");
return super._update(to, tokenId, auth);
}
}Built For
NFT Communities
Engage holders with quests that reward participation and loyalty.
DeFi Protocols
Incentivize protocol usage with points that convert to governance tokens.
Gaming
Build in-game achievement systems with on-chain rewards.
Brand Loyalty
Web3-native loyalty programs with tradeable reward tokens.
Start Building with Hanzo Quest
Get your free API key and ship your first request in under 5 minutes. No credit card required.