Skip to content

REFERENCE

Andy Colosimo edited this page Jun 12, 2026 · 1 revision

Verified file templates and outputs (Xaya GSP on Polygon)

Everything here was built, run, and tested live on Polygon mainnet (2026-06-12), porting the official mover example (game id mv). Adapt names/ids for your game. The complete worked project also ships in the Xaya tutorials wiki under examples/mover-polygon/.

1. The chain patch (mover/logic.cpp, GetInitialStateInternal)

    case Chain::POLYGON:
      /* The game state starts at a recent Polygon block.  Moves sent
         before this height are ignored.  The hash is left empty so that
         any block at this height is accepted (we trust the connected
         XayaX instance rather than pinning an exact block hash).  */
      height = 88365000;   /* pick a recent block <= current tip */
      hashHex = "";
      break;

Chain::POLYGON exists in the installed xayagame/gamelogic.hpp (also TEST, REGTEST, MAIN, MUMBAI, GANACHE). With empty hash, the GSP logs: Game did not specify genesis hash, retrieved <hash> and pins it itself.

2. GSP Dockerfile (verified; ~minutes to build)

FROM xaya/libxayagame AS build

RUN apt update && apt -y install build-essential pkg-config

# Version check: XayaX reports 1000000, stock minimum is 1010200.
# Alternative to this sed: set config.MinXayaVersion = 1000000; in main.cpp.
RUN sed -i 's/unsigned MinXayaVersion = 1010200/unsigned MinXayaVersion = 1000000/' \
    /usr/local/include/xayagame/defaultmain.hpp

WORKDIR /usr/src/mygame
COPY mover/ mover/

RUN protoc -Imover --cpp_out=mover mover/proto/mover.proto

# main.cpp includes config.h (normally generated by the build system).
RUN echo '#define PACKAGE_VERSION "polygon"' > config.h

RUN set -e && \
    CXXFLAGS="-std=c++14 -I. -Imover \
      $(pkg-config --cflags jsoncpp libglog protobuf sqlite3)" && \
    g++ $CXXFLAGS -c mover/proto/mover.pb.cc -o mover.pb.o && \
    g++ $CXXFLAGS -c mover/moves.cpp -o moves.o && \
    g++ $CXXFLAGS -c mover/logic.cpp -o logic.o && \
    g++ $CXXFLAGS -c mover/pending.cpp -o pending.o && \
    g++ $CXXFLAGS -c mover/main.cpp -o main.o && \
    g++ -o moverd main.o logic.o moves.o pending.o mover.pb.o \
      -L/usr/local/lib -lxayagame -lxayautil \
      $(pkg-config --libs jsoncpp libglog gflags protobuf sqlite3 \
        libcurl libzmq libmicrohttpd openssl zlib lmdb \
        libjsonrpccpp-client libjsonrpccpp-server libjsonrpccpp-common)

# cpld (provided by the base image) collects the shared-library closure.
WORKDIR /jail
RUN mkdir -p bin lib64 && cp /usr/src/mygame/moverd bin/ && cpld bin/moverd lib64

FROM debian:13-slim
COPY --from=build /jail /usr/local/
ENV LD_LIBRARY_PATH="/usr/local/lib64"
VOLUME ["/xayagame", "/log"]
ENV GLOG_log_dir="/log"
RUN mkdir -p /log /xayagame
EXPOSE 8600
ENTRYPOINT ["/usr/local/bin/moverd"]

Notes: only -lxayagame -lxayautil for simple games (-lgamechannel -lchannelcore -lethutils are channel-game extras). The image's own prebuilt moverd is unusable (linked against libmover.so which isn't installed).

3. docker-compose.yml (verified, including the subnet pin)

name: mover

services:
  xayax:
    image: xaya/xayax
    profiles: [xayax]
    restart: unless-stopped
    networks: [mover-net]
    ports:
      - "127.0.0.1:8101:8000"
    volumes:
      - xayax-data:/xayax
    command:
      - "eth"
      - "--eth_rpc_url=https://polygon-node.xaya.io"
      - "--accounts_contract=0x8C12253F71091b9582908C8a44F78870Ec6F304F"
      - "--port=8000"
      - "--listen_locally=false"
      - "--zmq_address=tcp://xayax:28555"
      - "--logtostderr"

  moverd:
    build: { context: .., dockerfile: docker/Dockerfile }
    restart: unless-stopped
    networks: [mover-net]
    ports:
      - "127.0.0.1:8602:8600"
    volumes:
      - moverd-data:/xayagame
      - moverd-logs:/log
    environment:
      - GLOG_alsologtostderr=1
    command:
      - "--xaya_rpc_url=http://xayax:8000"
      - "--xaya_rpc_protocol=2"
      - "--game_rpc_port=8600"
      - "--game_rpc_listen_locally=false"
      - "--storage_type=sqlite"
      - "--datadir=/xayagame"
      - "--enable_pruning=1000"
      - "--pending_moves=false"

networks:
  mover-net:
    ipam:
      config:
        # Pin explicitly!  Docker may auto-assign 192.168.0.0/20 when its
        # default pools are exhausted, shadowing the host's LAN routes.
        - subnet: 172.16.0.0/24

volumes:
  xayax-data: {}
  moverd-data: {}
  moverd-logs: {}

Launch: docker compose --profile xayax up -d --build. These XayaX flags are the complete verified set — flags like --eth_ws_url, --genesis_height, --max_reorg_depth are NOT part of the verified invocation; don't add them.

4. Expected GSP startup log (real lines)

Connected to RPC daemon with chain polygon
Connected to Xaya Core version 1000000
Detected ZMQ blocks endpoint: tcp://xayax:28555
Got genesis height from game: 88365000
Game did not specify genesis hash, retrieved 701ca380fb3ec9bf7e0abd3d4f89895dae229e1138c2fd471245b486568dba10
Retrieving 0 detach and 128 attach steps with reqtoken = request_461, ...

Catch-up of ~5300 blocks took ~2 minutes. Fresh XayaX (new volume) starts near the chain tip, serves RPC within ~2 minutes, and backfills older blocks on demand (a GSP genesis below the bridge's start height works fine — verified).

5. GSP RPC (port 8602 on the host in this setup)

curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getnullstate","params":[]}' \
  http://127.0.0.1:8602

Real output:

{"id":1,"jsonrpc":"2.0","result":{"blockhash":"6a4c5171...","chain":"polygon","gameid":"mv","height":88370225,"state":"up-to-date"}}

Methods: getcurrentstate (envelope + gamestate JSON), getnullstate (envelope only — cheap sync check), getpendingstate (errors via XayaX), waitforchange [knownBlockHash] (long-poll: valid stale hash → returns immediately; empty/unparseable hash → waits for next change or ~5 s internal timeout; on Polygon typically returns in ~2–3 s), waitforpendingchange, stop. Concurrent calls during a pending waitforchange are served fine (verified: 7 ms response). State values progress pregenesiscatching-upup-to-date.

6. Sending a move (verified live; viem)

Move envelope: {"g":{"mv":{"d":"k","n":2}}} — outer g, then game id, then your game's move object. Contract call:

walletClient.writeContract({
  address: '0x8C12253F71091b9582908C8a44F78870Ec6F304F',
  abi: moveAbi,   // move(string ns, string name, string mv, uint256 nonce, uint256 amount, address receiver)
  functionName: 'move',
  args: ['p', name, moveJson, 2n ** 256n - 1n, 0n,
         '0x0000000000000000000000000000000000000000'],
});

Real result: tx 0x21f9ad768d8556d1118d64d49556406dbd7c5bfd9e287c8151a5c19ea4d0ffd3 confirmed in block 88370257; GSP logged Processed 1 moves forward, new state has 1 players; getcurrentstate showed {"players":{"xsv5bob":{"x":0,"y":2}}}. (Mover semantics: the move's own block already executes step 1.)

7. Registering a name (read path verified; flow follows verified ABI)

fee = policy.checkRegistration('p', name)   // flat 1 WCHI, 8 decimals (verified live)
WCHI.approve(XayaAccounts, fee)             // if allowance < fee
XayaAccounts.register('p', name)            // mints the ERC-721 to the caller

XayaAccounts also has: exists(ns,name), policy(), wchiToken(), tokenIdForName, tokenIdToName, plus standard ERC-721 enumerable. Names are permanent; one name plays every Xaya game.

8. Determinism rules for game logic

  • State must be a pure function of the ordered move sequence.
  • Use protobuf for state/undo serialization (the mover pattern — verified bit-identical across independently synced GSPs). Don't depend on JSON key-ordering or hash-map iteration order.
  • No wall-clock time, no external I/O, no unseeded randomness, no floats in consensus-relevant math.
  • Invalid moves: log and skip (LOG(WARNING) << "Ignoring invalid move"), never abort — every GSP must make the same call on every input.

Clone this wiki locally