hackquest logo

Derby

The problem: a friend in Lagos and a friend in London want to bet a friendly $10 on tonight's match. Venmo/PayPal don't cross borders cleanly, nobody wants to be the "holds everyone's money" friend, a

ビデオ

テックスタック

Web3

説明

# Derby — trustless friendly wagers for World Cup matches

Built for the [Injective Global Cup](https://www.hackquest.io/hackathons/The-Injective-Global-Cup).

**The problem:** a friend in Lagos and a friend in London want to bet a
friendly $10 on tonight's match. Venmo/PayPal don't cross borders cleanly,
nobody wants to be the "holds everyone's money" friend, and someone has to
manually check the score and pay out.

**What Derby does:** anyone opens a pool for a fixture, friends join by
staking the same buy-in on home/draw/away, and once the match is final the
whole pot splits evenly across whoever picked the winning side — no
organizer ever touches the money, and no one has to manually check the score.

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ Agent Skill (skills/derby/SKILL.md)                          │
│   lets Claude (or any MCP client) drive the whole flow        │
│   conversationally — "set up a pool for tonight's match"       │
├─────────────────────────────────────────────────────────────┤
│ MCP Server (mcp-server/)                                      │
│   6 tools: create_pool, join_pool, check_score, settle_pool,  │
│   list_pools, get_pool                                        │
│   + an x402-gated /score HTTP endpoint (httpServer.ts) that   │
│     any client — not just this MCP server — can call directly │
├─────────────────────────────────────────────────────────────┤
│ derby-escrow contract (contracts/derby-escrow/, CosmWasm/Rust)│
│   pari-mutuel pool escrow: create/join/submit-result/settle/  │
│   cancel/refund. No house rake. No organizer-withdraw path.   │
└─────────────────────────────────────────────────────────────┘
                        │
                        ▼
              Injective (testnet in this build)
```

## Injective tech used, and why

- **MCP Server** — the actual interface. Every action (create a pool, join,
  check a score, settle) is a typed tool an agent can call directly, not a
  wrapper bolted onto a web UI afterward.
- **x402**`GET /score/:fixtureId` is genuinely payment-gated. It returns
  HTTP 402 with an x402-shaped payment requirement until the caller has paid
  `PAY_TO_ADDRESS` the quoted USDC amount and retries with the resulting tx
  hash as proof. See "x402 on Injective" below for why this is a from-scratch
  implementation rather than an off-the-shelf middleware package.
- **Agent Skills**`skills/derby/SKILL.md` teaches an agent the actual
  domain flow (find the fixture, create, join, settle) and the custody model,
  not just the raw tool signatures.

**CCTP was deliberately left out of this build.** Cross-chain funding (a
friend on Base, a friend on Arbitrum, both joining an Injective-native pool)
is the natural next integration, but wiring up Circle's CCTP flow plus the
UI for it is a multi-day task on its own, and this was built in two days.
Every pool in this build is funded directly in the Injective-native buy-in
denom. See "Roadmap" below.

## x402 on Injective

The official x402 middleware packages (`@x402/express`, `@x402/evm`,
`@x402/svm`, `@x402/avm`) cover Base, Solana, Stacks, and Algorand as of this
build — not Injective/Cosmos. Rather than force an unsupported network
string into a scheme built for a different curve, `mcp-server/src/x402.ts`
implements the protocol directly against Injective's LCD:

1. Client requests `/score/:fixtureId` with no `X-PAYMENT` header.
2. Server replies `402` with an x402-shaped `accepts` array.
3. Client pays the quoted amount to `payTo` with a normal bank-module
   `MsgSend` (Injective finalizes in ~1 block, so this checks a *settled* tx
   rather than the sign-but-don't-broadcast pattern some x402 schemes use for
   slower chains).
4. Client retries with `X-PAYMENT: <tx hash>`. The server fetches that tx
   from the LCD (`/cosmos/tx/v1beta1/txs/{hash}`), confirms a `transfer`
   event paying `payTo` at least the required amount in the required denom,
   and only then serves the resource.

`src/x402Client.ts` is the matching client half, used by the `check_score`
tool so the round trip is invisible to whoever's driving the agent — but any
other client (a curl script, a competing app) can hit `/score/:fixtureId`
directly and would have to pay the same way. The check is a real chain
query, not a signature the server takes on faith.

## Custody model — read before running this

This is a two-day hackathon build, so it makes one deliberate trust
trade-off: **the MCP server derives four demo accounts (oracle, alice, bob,
carol) from one relayer mnemonic** via HD paths `m/44'/60'/0'/0/{0,1,2,3}`,
so a judge can run the full create → join → submit-result → settle flow
through MCP tool calls alone, without standing up a separate wallet UI first.

The *contract* itself is genuinely non-custodial regardless of who signs:
`JoinPool` has no special-casing for these demo accounts, there is no
organizer-withdraw path, and `ClaimRefund` lets any participant recover their
own stake if the oracle goes dark after the refund deadline. Swapping the
alice/bob/carol demo signers for real user wallets (Keplr/Leap/MetaMask via
Injective's wallet strategies) is a frontend change, not a contract change —
see Roadmap.

## Repo layout

```
contracts/derby-escrow/   Rust/CosmWasm escrow contract
  src/state.rs              Config, Pool, Side, PoolStatus
  src/msg.rs                Instantiate/Execute/Query messages
  src/contract.rs           entry points + all execute/query logic
  tests/integration.rs      6 tests: full settle flow, no-winners refund,
                             refund-deadline claim, cancel+refund, and two
                             tests that lock in the exact JSON wire format
                             the TS server relies on
mcp-server/               TypeScript MCP server + x402 score API
  src/chain.ts               Injective signing/querying (eth_secp256k1 —
                              see comment at top of file for why plain
                              CosmJS wallets don't work here)
  src/x402.ts / x402Client.ts  the x402 implementation described above
  src/scoreApi.ts            TheSportsDB live-score lookup
  src/tools/                 one file per MCP tool
  src/index.ts                MCP entrypoint (stdio transport)
  src/httpServer.ts           the paywalled /score endpoint
skills/derby/SKILL.md      Agent Skill for conversational use
```

## Running it

### 1. Contract

```bash
cd contracts/derby-escrow
cargo test                      # 6 tests, all passing as of this build
rustup target add wasm32-unknown-unknown   # if not already installed
cargo wasm                      # or: docker run --rm -v "$(pwd)":/code \
                                 #     cosmwasm/optimizer:0.16.0
injectived tx wasm store artifacts/derby_escrow.wasm --from <key> \
  --chain-id injective-888 --node <testnet-rpc> --gas auto --gas-adjustment 1.3
injectived tx wasm instantiate <code_id> \
  '{"admin":"<oracle-injective-address>","denom":"<testnet-usdc-denom>"}' \
  --from <key> --label derby-escrow --no-admin --chain-id injective-888
```

Fill the resulting contract address into `mcp-server/.env`.

### 2. MCP server

```bash
cd mcp-server
cp .env.example .env    # fill in CONTRACT_ADDRESS, DENOM, RELAYER_MNEMONIC, PAY_TO_ADDRESS
npm install
npm run typecheck        # confirms clean as of this build
```

Fund all four derived demo accounts (see Custody model) from the
[Injective testnet faucet](https://testnet.faucet.injective.network/) before
running any flow — `create_pool`/`join_pool`/`settle_pool` all pay gas.

Run the x402 score API and the MCP server in separate terminals:

```bash
npm run http    # the paywalled /score endpoint, needed by check_score
npm run mcp     # the MCP server itself (stdio) — point your MCP client at this
```

### 3. Agent Skill

Point Claude (or any MCP-aware agent) at the running MCP server and load
`skills/derby/SKILL.md`. Then just talk to it: "set up a $10 pool for
tonight's Nigeria match, I'll take home, put bob on away."

## What's real, what's not

Real: the contract logic (tested), the MCP tool wiring (typechecked end to
end against the contract's actual serialized wire format — see the two
JSON-shape tests in `tests/integration.rs`), the x402 protocol implementation
against Injective's LCD, the eth_secp256k1 signing via `@injectivelabs/sdk-ts`.

Not yet run against a live chain: this build was verified with `cargo test`
(contract) and `tsc --noEmit` (server) inside a sandboxed environment without
network access to a running Injective node or a wasm32 Rust target, so the
`cargo wasm` / `injectived tx wasm store` / faucet-funded live flow above is
written but untested end-to-end. Treat the deploy steps as the next thing to
run, not something already confirmed on testnet.

## Security note: a supply-chain incident in this exact dependency

`@injectivelabs/sdk-ts` (and ~17 sibling `@injectivelabs/*` packages) had a
version briefly compromised — a malicious hook on `PrivateKey.fromMnemonic`
that exfiltrated mnemonics — published as `1.20.21` and fixed in `1.20.23`.
`package.json` pins `^1.20.23` specifically so npm's resolver can never land
on the compromised version. If you're auditing this repo, check that pin
before running anything that touches `RELAYER_MNEMONIC`.

## Roadmap

- **CCTP**: let a friend on Base or Arbitrum join an Injective-native pool
  by depositing USDC on their own chain; Circle's CCTP handles the burn/mint
  into the pool's denom.
- **Real user wallets**: drop the alice/bob/carol demo signers, have
  `join_pool` return an unsigned `MsgExecuteContractCompat` for the user's own
  wallet (Keplr/Leap/MetaMask) to sign, so the MCP server never touches
  participant funds — only the oracle key remains server-side.
- **Independent oracle**: replace the single relayer-as-oracle with a real
  sports-data oracle module or an M-of-N multisig of independent reporters,
  so no single key can misreport a result.

ハッカソンの進行状況

Built and working:

  • Contract logic — full CosmWasm escrow (create/join/submit-result/settle/cancel/refund) with 6 passing tests, including two that lock in the exact JSON wire format the TS server depends on

  • MCP server — all 6 tools (create_pool, join_pool, check_score, settle_pool, list_pools, get_pool), typechecks clean end-to-end against the contract's actual serialized format

  • x402 payment-gating — a real from-scratch implementation against Injective's LCD (not an off-the-shelf package, since none of the official x402 middlewares support Injective/Cosmos yet) — genuinely gates the /score endpoint on a settled on-chain payment, not just a signature

  • Agent Skill — the conversational flow ("set up a $10 pool for tonight's match...") is written and wired to the MCP tools

  • Custody model — non-custodial by design: no organizer-withdraw path, refund claim available if the oracle goes dark

資金調達の状況

N/A

チームリーダー
AAkachukwu David
プロジェクトリンク
業界
DeFi