Hey great work on the game! I actually did try it quite some time ago but I’ll have to try it again!
I guess the real question is whether or not you can attract a significant amount of players.
What is the marketing plan? Just social media or you have some strategy to gain and hopefully retain users?
What other project or projects are you working on? You mentioned the WCHL 25…
Hey @jokerswild, thank you for the kind words! I really appreciate it. ![]()
Right now, We’re fully focused on the CMHunt project. We also competed with it in WCHL 25 and made a lot of progress over the past few months. Unfortunately, we weren’t fast enough to update the DoraHacks page with the shorter 3-min pitch and 2-min demo videos in time (was fixing a critical bug), so we were excluded from the top 30 projects that will pitch. Everything is updated now, though—you can check out detailed info, video explanations, and progress here: CMHunt | Buidls | DoraHacks
For marketing, our current plan is mainly social media reach. Soon, we’ll implement a human verification feature to reward verified users and their referrers, which should help grow and retain our user base.
A bit about the team: We come from the gamedev space. I personally started in 2009 working with Unity/C#. For the past three years, we’ve focused on research and development on the ICP blockchain, working with Motoko, React, and Typescript. You can also check out some of our previous ICP Web3 projects at the start of this forum post. ![]()
I want to create an MCP server so I can tell my agent my mining strategy and it will control my drill. It looks like the movement and player positions are not tracked on chain and I’m afraid the experience would be bad for other users to see my mine popping around the map instantly. Do you have any plans to persist miner position and movement on chain?
Hey @yrgg, Thanks for your interest in the project.
Since blockchain state updates take a few seconds to process, we can’t achieve smooth, real-time position updates like in traditional Web2 games. Currently, a player’s position is stored on the blockchain but only updated when the player is mining (i.e., when the mineTile(row: nat, col: nat) function is called).
It’s technically possible to update the position more frequently (for example, by calling mineTile periodically), but we don’t think it’s necessary at this point.
Only players within your visible area are synced, and those who are actively mining naturally update their positions through mining actions. Other players’ positions are interpolated smoothly between their previous and current updates, making movement appear continuous and natural.
In short, if your agent calls mineTile in a linear or gradual pattern, other players will see your miner moving smoothly across the map rather than jumping around. ![]()
So, I went and started creating the ai agent autopilot but realized that to interact with the world you need to use composite query calls, which you cannot do from a shared func so I don’t think it’s possible to have a canister controlling the mining rig.
Would it be possible to have normal query versions in addition to the composite versions?
Ahh… yes — I’m using composite queries because they can’t be executed in replicated mode. Standard queries can still be triggered as replicated/update calls, which opens the door for cycle drain or DoS. Composite queries avoid that by allowing only ingress calls.
All calls are also restricted to registered users, so your canister is currently rejected for the same reason (failing fast against untrusted callers).
Given that, the only workable approach is to expose separate standard-query endpoints protected by a trusted-canister allowlist (and optional rate limiting).
If you let me know which functions you need to call — and the ID of your AI canister — I can add the appropriate standard-query endpoints so your canister will be allowed to call them.
You can have an extra query method specifically for other canisters which charges some cycles. The cycles have to come with the call.
Yeah that’s a good idea. Whitelisting a canister would work, except it’s not just one canister. Users each get their own canister since CM Hunt uses msg.caller for ownership so each controller needs their own principal. Paying a small cycle cost would work though if the idea is to prevent ddos or cycle drains.
As far as the account registration, that looked like a simple mutation call which the AI control canisters could do in some startup code or something. We already do something similar with the CycleOps integration.
Thanks, @timo — that’s actually a much cleaner approach. Charging a small amount of cycles on a dedicated canister-to-canister call would indeed prevent abuse. One note though: according to the AI assistant on Motoko docs page, query methods can’t accept cycles, only update calls can, but that doesn’t matter in this case since any inter-canister call (query or update) executes in replicated mode anyway, the cost/overhead ends up being the same.
@yrgg — understood about each user having their own canister. Given the above, update-based endpoints are probably the safest and simplest route.
To set things up on my end, could you confirm exactly which functions your canisters need to call?
My guess is:
- getTiles — fetch the local 2D tile window around the mining position
- getStats — current world’s remaining tiles / rewards info
- getMyData — drill HP and per-player stats
- getMyBombsInfo — if you need bomb state / counts
If those are the ones, I’ll create update variants specifically for canister callers.
For reference, here’s one of the world canisters you can inspect:
5hmgu-wiaaa-aaaad-aajza-cai.
Just let me know which endpoints you need and I’ll add the appropriate update calls. ![]()
Let’s walk through cycle drain attacks again from the beginning to recap.
First consider attacks by calls from outside (external principal). The calls are either
replicated = update calls + replicated queries, or
non-replicated = normal queries.
Non-replicated calls are free so not your concern. All replicated calls (from outside) go through inspect_message, which is your first line of defense.
In inspect_message you can put access control in it and filter for arguments and argument sizes etc. to make sure only your legitimate users can burn your cycles and not too much. You can also block replicated queries altogether if that’s what you want. For that you define inspect_message for your query function that always returns false. Then that function can’t be called in replicated mode anymore. It still can be called in normal query mode because inspect_message isn’t invoked for those.
This so far should take care of calls that originate from outside the IC
Next consider attacks by calls from inside, i.e. from another canister. Generally I would not be too worried about them. You can likely ignore them. They are harder to pull off, the attacker has to write a canister first. But most importantly the inter-canister calls cost the attacker cycles. So now it is a game of the ratio between how many cycles you lose per call and how many cycles the call costs for the attacker. If you can control the cycle consumption in your calls down to a reasonable amount (regardless of whether it is semantically an update or a query) then likely the ratio isn’t good for the attacker. That depends on your application and implementation. If your cycle consumption per call is high (say 100M+ or 1B+ cycles) then you can think of charging cycles (what we discussed above) or blocking certain calls entirely.
To block calls from other canister you could look at msg.caller and if its a 10-byte principal then you know its from another canister and you can reject the call. Note that there is no inspect_message for inter-canister calls. If you reject a call after looking at msg.caller in the call itself (not in inspect_message which you don’t have here) then the invocation of the call alone will have cost you some cycles. But my argument from above about the ratio applies here. The ratio is simply not good enough for the attacker to make it viable.
Yeah, I think those are the required methods on the world canister, but you definitely know better than me. Let me know if this sounds close to the required flow:
On canister creation we need to be able to call ‘amIregisteredUser’ and ‘registerMe’ if false. That will give our canister an account with a default drill.
We will need match discovery, though initially I had just planned to use the public PVE match (getAvailablePvEWorld), but having match discovery would be a much better experience. So I guess ‘listAvailableMatches’ and ‘getMatchDetails’ would enable that process.
Then the ai agent will have an account and a drill, can find a match and inspect it, and then join it if there is $$ opportunity.
For world canister stuff the ones you stated seem exactly right. The ai agent needs to be able to explore the map (getTiles), examine the matches stats (getStats), keep an eye on its resources (getMyData), and as you say ‘getMyBombsInfo’.
I’m assuming the purchaseItem and purchaseVehicle use icrc2 approve/transfer_from? Where do they see the catalog of available items/vehicles to find out the item id and price?
Let me know if I missed any, but from going through the process I think that covers everything?
Hey @timo, Thanks a lot for the detailed breakdown and recap — super useful as always. ![]()
I’m already relying heavily on inspect_message for update/replicated calls, but I hadn’t considered using it the way you described — specifically, to prevent outside users from calling query functions in replicated mode.
With that in mind, I can drop the composite-query workaround and just use normal queries while having inspect_message return false for them. External ICP users won’t be able to run those queries in replicated mode, and inter-canister calls will still work fine. Your suggestion is probably the cleanest and easiest approach for my case — thanks for that!
Hey @yrgg, sorry for the delayed response! Really glad to hear you’re looking to integrate your AI agent into the project — I’m genuinely curious to see how an AI ends up playing the game on its own, especially when it ventures into PvP matches ![]()
![]()
Yep, You got the flow exactly right. ![]()
Regarding purchases:
- purchaseItem / purchaseVehicle were originally hardcoded because I expected only the frontend to call them.
- For purchaseItem you can currently send
"Drill"or"DrillDiscounted", which use ICRC-2approve + transfer_fromand cost ICP. But I’m planning to change drilling to be payable only in native CMHT, since the ICP → CMHT value ratio isn’t great for players right now. - purchaseVehicle takes an ID from
0to14, matching the vehicles shown in the Shop (IDs progress horizontally). These are purely cosmetic for now — leaderboard/chat flexing only — so your AI probably won’t care about such human instincts
Seems like you went through the system thoroughly, and yep, everything you described matches how the flow works today.
Awesome! Let me know when the new interface is ready and I’ll plug it in!
Announcement: CMHT Powering the Game! (Tokenomics Update)
Miners, get ready! Our economy just became fairer and fully decentralized.
CMHT: The New Engine
To boost utility and create a market-driven economy, Drill HP is now primarily purchased using CMHT tokens!
This change ensures:
- CMHT Utility: Your tokens are now essential fuel for progress.
- Fair Pricing: DEX supply/demand sets prices, removing the unprofitable ICP ratio.
- Incentive: The game is alive and competitive again!
Updated Costs (in CMHT)
| Item | New Cost/Value |
|---|---|
| 100 Drill HP | 1200 CMHT |
| 600 Drill HP | 7000 CMHT |
| V/H Bombs | 80 CMHT |
| Square Bomb | 500 CMHT |
Jump in and utilize your CMHT! The market dictates the value now.
Hey @yrgg,
Changes are LIVE! You’re welcome to start testing your AI agents now ![]()
![]()
Nice! I’ll work on it and keep you updated.