Gully Quests
On this page
Gully Quests is the on-chain quest system on Gully Chain. A quest says what to do (score at least 5,000 in a season, hold a token, own a .gully name), how many players can complete it, and what each completion pays from a funded pool. The chain itself checks the action through a verifier contract, so a completion is proof, not a claim.
For Gully Studio games, quests are achievements with rewards: the studio creates them, players complete them from inside the game, and the completion is recorded on chain. Advertiser-funded quests use the same contracts through the Offerwall.
Live
Gully Quests v1.0 is live on Gully Chain Testnet (chain 485590) since 2026-10-26, with three seed quests.
#Contracts (testnet)
| Contract | Address |
|---|---|
| QuestRegistry | 0xE3E57e15A9973281A8de2B3bedd512BE8C3813Bb (opens in a new tab) |
| QuestEscrow (holds reward pools) | 0xbAcaB79c8F22cA82eeC6ee2D6200Fb9FBc7EcEfD (opens in a new tab) |
| GullyReputation (anti-sybil gate) | 0x2cd00C780AD9E395d79130aAdc6306a058B43120 (opens in a new tab) |
| ArcadeScoreVerifier | 0x6c8e6C29333A8ece85B819F501Af5D160637F0d4 (opens in a new tab) |
| AttestedVerifier | 0x71295ef91ddBd5C174BBA3D9144a7cCDb3662443 (opens in a new tab) |
Other verifiers check .gully names, token holdings, launchpad trades, swaps, bridging and referrals. Reward assets allowed on testnet: ETH, glUSD, USDC.e and WETH.
#The quest struct
quest(uint256 questId) returns:
| Field | Type | Meaning |
|---|---|---|
creator |
address | who created and funds it |
start, end |
uint64 | open window, unix seconds (end exclusive) |
maxCompletions, completions |
uint32 | cap and current count |
rewardAsset |
address | 0x0 means ETH |
rewardAmount |
uint256 | paid per completion (Direct); "up to" in Merkle mode |
verifier |
address | the contract that checks the action |
mode |
enum | 0 Direct, 1 Merkle |
status |
enum | 0 Active, 1 Paused, 2 Cancelled |
requiredReputation |
uint8 | GullyReputation flags: 1 = has a .gully name, 2 = account age |
minAccountAgeBlocks |
uint32 | used with the account-age flag |
merkleRoot, rootPostedAt |
bytes32, uint64 | Merkle mode only |
title, metadataURI |
string | display title, link to metadata |
#Reward modes
| Mode | How players get paid |
|---|---|
| Direct | each completion is paid at once from the pool, in the same transaction |
| Merkle | completions are recorded; after the end the creator posts a Merkle root and players claim with claimReward |
#Reading quests from Unity
The SDK has no dedicated quests client yet, so read with JsonRpcClient and Abi:
| Function | Selector |
|---|---|
questCount() |
0x3970ab43 |
quest(uint256) |
0xe24b3cea |
available(uint256) (pool balance) |
0x96e494e8 |
completedAt(uint256,address) |
0x96c75086 |
canComplete(uint256,address,bytes) |
0x1600e590 |
complete(uint256,bytes) |
0x16822c98 |
using GullyLabs.GameSdk;
const string Registry = "0xE3E57e15A9973281A8de2B3bedd512BE8C3813Bb";
var rpc = new JsonRpcClient();
var count = Abi.DecodeUint(Abi.Words(await rpc.EthCallAsync(Registry, Abi.Call("questCount()")))[0]);
// pool left for quest 1
var pool = Abi.DecodeUint(Abi.Words(await rpc.EthCallAsync(Registry, Abi.Call("available(uint256)", Abi.Uint(1))))[0]);
// has this player completed quest 1? (block number, 0 = no)
var done = Abi.DecodeUint(Abi.Words(await rpc.EthCallAsync(Registry,
Abi.Call("completedAt(uint256,address)", Abi.Uint(1), Abi.Address(player))))[0]);quest(uint256) returns a struct with two strings at the end. Decode its head with Abi.Words (each field is one 32-byte word in the order of the table above, after the leading tuple offset) and the strings with their offsets, or keep a server-side cache of quest metadata for display.
#Check before the player pays gas
canComplete(questId, account, proof) is a dry run of complete. It returns (bool ok, uint8 reason, bytes32 evidence):
| Reason | Meaning |
|---|---|
| 0 | ok |
| 1 | not active (paused or cancelled) |
| 2 | outside the start and end window |
| 3 | already completed |
| 4 | full (maxCompletions reached) |
| 5 | reputation gate not met |
| 6 | the verifier said no |
| 7 | reward pool empty |
Show the reason in the UI instead of letting a transaction revert.
#Completing a quest
complete(questId, proof) must be sent from the player's account (the registry uses msg.sender), through your ITransactionSender:
byte[] proof = new byte[0]; // most verifiers need no proof; attested quests pass the attestation here
string data = Abi.Call("complete(uint256,bytes)", Abi.Uint(questId), Abi.Uint(64), Abi.DynamicBytes(proof));
string tx = await sender.SendTransactionAsync(new TransactionRequest { To = Registry, Data = data });A successful completion emits QuestCompleted(questId, account, blockNumber, evidence, reward) and, in Direct mode, pays the reward in the same transaction.
#Arcade score quests
ArcadeScoreVerifier reads GullyArcade.bestScore(seasonId, account), which is already attested when the score is submitted. Its params are abi.encode(uint256 gameId, uint256 seasonId, uint256 minScore); seasonId = 0 means the latest season of the game. So "score 5,000 in Frog Hop" needs no extra proof: submit the score with GullyArcadeClient, then call complete.
#Creating achievement quests
Achievement quests are created by the studio, not by the game client: createQuest with the verifier and params, then fund (ETH) or fundERC20. A 5% fee is taken on funding. The creator can pause, cancel and withdraw unclaimed rewards; the registry owner can never move a pool.
Coming soon
A QuestsClient in the Gully Game SDK (typed quest reads, canComplete, complete) is not built yet. Until then, use the calls above.