-
Notifications
You must be signed in to change notification settings - Fork 10
REFERENCE
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/.
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.
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).
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.
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).
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getnullstate","params":[]}' \
http://127.0.0.1:8602Real 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 pregenesis → catching-up
→ up-to-date.
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.)
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.
- 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.
Xaya developer documentation — verified on Polygon mainnet. Questions? Xaya Development Discord