DEV Community

Cover image for Building Cubbyz: a non-custodial Telegram escrow where Gemini agents judge but never hold the money
Ron Coburn
Ron Coburn

Posted on

Building Cubbyz: a non-custodial Telegram escrow where Gemini agents judge but never hold the money

I wrote this post for the purposes of entering the All Things Agentic Hackathon (Google / Devpost, August 2026). Cubbyz is my Taskmaster-track submission. Try it at t.me/cubbyz_test_bot — Base Sepolia testnet only, no real funds.

The friction I brought

Money I had on deposit at a bank moved without my authorization, and then the bank failed. I had trusted an institution to hold my funds; the institution had the ability to move them; it did. There was nothing I could have signed that would have stopped it.

That is the "Bring Your Own Friction" I built around: every system where a third party can move your money is a system where you are hoping they don't. Deals between strangers on Telegram run on that same hope. Existing escrow bots just move the hope to a middleman who holds the funds.

Cubbyz is my answer: custody without a custodian, with AI agents that do the judging and are structurally unable to do the holding.

What it is

  • A Telegram bot walks a buyer and seller through create → fund → deliver → release.
  • Funds lock in a CubbyzEscrow smart contract on Base Sepolia (test USDC). Every state-changing action is a transaction the user signs in their own wallet. No server ever holds a key.
  • A crew of four agents runs on Google Cloud Run, reasoning with Gemini 3.5 Flash via Vertex AI through the Google GenAI SDK. The one I'm proudest of is A3 Verification: every 30 minutes it sweeps funded escrows with submitted work, inspects the actual artifacts (including images), compares them to what was agreed, and writes a verdict to the database. Nobody asks it to.

The one rule that shaped everything

No API may mutate escrow status. On-chain evidence is the only route to funded, delivered, or released.

This sounds like a slogan until you try to build a product around it. There is no "I've paid" button, because a button is a trusted write. A detection job reads the chain every 60 seconds and is the only thing allowed to flip status. When I wanted a "refresh status" feature, it could only re-run the read. When I found a custodial key-derivation path left over from an earlier prototype, the fix was deletion.

The payoff is a guarantee I can state without qualification: the strongest thing Cubbyz ships is an API that does not exist.

How the agent layer is wired

Telegram bot (Fly.io) --bearer token--> Cloud Run: cubbyz-agent-service
                                              |
                                              +--> Vertex AI: gemini-3.5-flash (location: global)
                                              |
                                              +--> Postgres: verification_verdicts
Enter fullscreen mode Exit fullscreen mode

The agent service is a separate Node deployment on Cloud Run under a dedicated service account (cubbyz-agents, aiplatform.user). The bot calls it with a bearer token; it never receives a wallet key because there is none to receive. The SDK call is @google/genai with vertexai: true.

One gotcha worth writing down: the 3.x Gemini models 404'd for me on us-central1. Setting VERTEX_LOCATION=global fixed it.

Agents have authority over facts — what was delivered, does it match — and none over funds. A5 Adjudicator weighs evidence when a dispute is opened and treats seller captions as untrusted input, because a caption is a claim, not a proof.

Three problems that cost real time

Stale RPC reads on an irreversible step. A public RPC node served yesterday's token allowance, so the first fund attempt skipped the approve and reverted. The fix: a dedicated Alchemy endpoint plus a bounded self-heal — approve, wait for the receipt, retry exactly once, and only when the receipt proves a revert. A timeout is not evidence of failure; a backgrounded mobile webview can drop the promise while the transaction succeeds on chain, and retrying on that would double-fund.

Telegram webviews and auth. Artifact previews 401'd the moment they were opened outside Telegram. I rebuilt the seller's /submit mini app around authenticated in-app previews and a proxied download that never exposes an unauthenticated URL.

Keeping the buyer's inbox honest. Batched uploads produced stale "Files: N" counts and orphaned messages. Now there is exactly one DM per batch, edited in place as files are added, so the count the buyer sees is always the count in the database.

The evidence rule

Fly log retention is minutes. Early on I "proved" things from CLI success strings and lost the proof by the next session. So I adopted a strict hierarchy for calling anything done: chain/DB state beats logs beats CLI output beats screenshots. Every claim in the submission has a database row or a two-device screen behind it — including the moment a seller uploaded a 4.6 MB image, the sweep fired on its own, Gemini inspected it, and a verdict row landed with its reasoning.

A related lesson: a clean type-check proves less than it looks. One import pointed at the wrong chain module and compiled fine. A database probe caught it.

What's next

A Telegram Mini App with in-chat WalletConnect signing (spec written, prototype built and audited), A3 acting on uncertain verdicts instead of staying silent, agents as escrow counterparties on the same contract, and a developer SDK so any bot or agent framework can embed the escrow.

Because hope isn't a payment method.


Stack: Gemini 3.5 Flash · Vertex AI · Google GenAI SDK · Google Cloud Run · TypeScript/Node · Fly.io + Fly Postgres · Next.js on Vercel · viem/wagmi · Base Sepolia · Phantom.

Top comments (0)