Available Now

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.

Back to Web3 Overview

Get Your API Key

Start building in under 5 minutes

99.999%
Uptime
<50ms
Latency
100+
Chains

No credit card required. Free tier includes 300M compute units/month.

Trusted by developers building:

DeFiNFTsPaymentsGamingAI Agents

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.

QuestRewards.sol
// 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.

Get Hanzo Quest

Deploy in seconds or self-host with the open-source release.