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)

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
C#
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:

C#
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.