Smart Model coming next
A coin slot as a 3D model that you put into a scene: a press asks for 1¢, and the game gets a "paid" event when the payment is confirmed.
Build · nano133 Kit Alpha. Works end to end and has tests, but it is new and can change.Use at your own risk, for education. Real Nano moves: keep amounts small.What the stages mean
Your game can pay the winner the moment a round ends, with no fee. Play the game, then copy the code.
How it works
It does not send a count of coins.
The server started the run itself. It decides the win.
A list that is no run is refused, and so is a run that ends sooner than its own length.
A prize limit for each player and a total for each day.
The computers of the Nano network vote on the payment.
On this page: the server's check of a run is real, and so is the Nano. A run of all 10 coins can make one claim at this site's faucet, with the faucet's own rules and limits: a new wallet, adults only, a few claims a day. It is the same claim that the faucet page gives with no game: the same amount, one time for a wallet. A win adds no Nano to it. So it is the faucet's gift, not a prize of the game, and this site has no prize wallet. The game says before the run when the faucet gives nothing. The arcade does the other half: its 1¢ rounds take their payment with Nano Checkout's amount and payment check, and the server plays each round again from its inputs (or, for the reaction test, checks its times) before it counts.
@nano133/three does the server part for you: it starts a round, checks it with a replay, and pays the prize once. It is on npm, under the MIT license.
npm install @nano133/three
Its Firestore store uses firebase-admin, an optional peer: install it in your server app if you use that store. The package holds no wallet and no key; your server signs the payment.
Four small files. The first one is the same file that the game on this page and this site's server run.
Plain three.js: the picture, and the jumps of the run.
// game.js: plain three.js. It draws the run and records the jumps; run.js is the rule.
import * as THREE from "three";
import { BLOCKS, COINS, RUN, start, step } from "./run.js";
const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector("#game"), antialias: true });
const scene = new THREE.Scene();
// A side view: 8 units of the course across.
const camera = new THREE.OrthographicCamera(-4, 4, 3.4, -1.1, 0.1, 20);
camera.position.z = 10;
scene.add(new THREE.AmbientLight(0xffffff, 0.8), new THREE.DirectionalLight(0xffffff, 1.4));
const box = (w, h, color) => new THREE.Mesh(new THREE.BoxGeometry(w, h, 0.5), new THREE.MeshStandardMaterial({ color }));
const floor = box(RUN.end + 20, 0.1, 0x35a8ff);
floor.position.set(RUN.end / 2, -0.05, 0);
const runner = box(RUN.wide, RUN.tall, 0xe9e9ee);
const coins = COINS.map((c) => {
const m = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 0.08, 24), new THREE.MeshStandardMaterial({ color: 0x209ce9 }));
m.rotation.x = Math.PI / 2;
m.position.set(c.x, c.y, 0);
return m;
});
const blocks = BLOCKS.map((b) => {
const m = box(b.wide, b.tall, 0xff6bd6);
m.position.set(b.x, b.tall / 2, 0);
return m;
});
scene.add(floor, runner, ...coins, ...blocks);
// 1. Ask YOUR server for a run. The server keeps the run's start time.
const { runId } = await (await fetch("/api/run", { method: "POST" })).json();
let pressed = false;
addEventListener("pointerdown", () => (pressed = true));
addEventListener("keydown", (e) => e.code === "Space" && (pressed = true));
const s = start();
const jumps = []; // the step number of each jump: this is what the server replays
const stepMs = 1000 / RUN.hz;
let last = performance.now();
let owed = 0;
renderer.setAnimationLoop(async () => {
// 2. The rule moves in whole steps of 1/60 second, whatever the screen's speed is.
const now = performance.now();
owed += Math.min(now - last, 100);
last = now;
while (owed >= stepMs && !s.done) {
owed -= stepMs;
const jump = pressed && s.y === 0; // a jump counts only on the ground
pressed = false;
if (jump) jumps.push(s.step);
step(s, jump);
}
runner.position.set(s.x, s.y + RUN.tall / 2, 0);
coins.forEach((m, i) => (m.visible = !s.coins[i]));
camera.position.x = s.x + 2;
renderer.render(scene, camera);
// 3. The run is over: send the jumps. The server counts the coins, and pays.
if (s.done) {
renderer.setAnimationLoop(null);
const send = async () => (await fetch("/api/run/" + runId, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ jumps }) })).json();
let answer = await send();
// The server's clock for the run started a moment after the game's: when it says "too soon", wait and send again.
while (answer.waitMs) {
await new Promise((r) => setTimeout(r, answer.waitMs));
answer = await send();
}
showResult(answer); // { won, coins, paid }
}
});
Paste this into Claude, ChatGPT, Gemini or your coding agent, in your game's repository:
Add "pay the winner" to this Three.js game with Nano (XNO): when a player wins a round, my server sends a small prize to the player's Nano address. Follow this spec step by step: https://nano133.com/build/three/SPEC.md Rules: the game's rule must be one pure file that the browser and the server both run; the server starts each round (and makes its seed, if the game has chance), replays the inputs and decides the win; the wallet key stays on the server in a secret manager and is never printed, logged or committed; add a prize limit for each player and for each day; make each prize send exactly once. Ask me for the prize amount, the limits, the node to use and how players sign in. Test with no real money first.
The agent follows the step-by-step spec, with the package or with the code to copy.
A coin slot as a 3D model that you put into a scene: a press asks for 1¢, and the game gets a "paid" event when the payment is confirmed.
Any mesh as a thing to pay: a door that opens, a look that changes, a sign that takes a tip.