Ship on-chain items in ten minutes
A studio integrates WardWallet in three surfaces: a game engine (Unreal), a web app (the TypeScript SDK), and a backend (a plain HTTP API). Every path goes through the same signed guardrails.
Quickstart
Run the whole stack locally — Postgres, a dev chain, and the server — and land your first transaction without touching a testnet.
# postgres + anvil (chain 31337) + the server cd deploy && MASTER_KEY_HEX=$(openssl rand -hex 32) ADMIN_EMAILS=you@studio.com docker compose up -d docker compose logs -f server # sign-in codes print here while SMTP is unset
Open the dashboard, sign in with the emailed code, create an app, and follow its checklist: register a contract with its ABI, define an action, sign and publish the policy, paste the setup key into the plugin, watch the first transaction land in Activity.
Actions — zero crypto in gameplay code
Define an operation once in the dashboard: buy_gems = deposit(player), amounts in human units. Then call it by name. No ABI, no wei, no nonces, no gas math in your game.
// player signs in, wallet created on the device Create Widget (Ward Login Widget) → Add to Viewport // the purchase: returns a confirmed tx hash Do Action "buy_gems" { amount : "0.5" }
import { WardWalletClient, doAction } from '@wardwallet/sdk'; const client = new WardWalletClient({ baseUrl }); await client.otpVerify(APP_ID, CLIENT_KEY, email, code); await doAction(client, { account, name: 'buy_gems', params: { amount: '0.5' }, idempotencyKey: orderId });
Named actions are the comfortable path, not the only one. Drop a level and keep the same guardrails: registered contracts, inline ABI passed in the request, raw calldata, or a read-only JSON-RPC proxy. Going lower buys more freedom of expression, never more rights — what the signed policy forbids stays forbidden, even in raw calldata.
The signed policy
The rules — which contract functions are callable, per-player daily caps, which calls get their gas paid — are a document signed with an Ed25519 key that stays in your browser. The server holds the public key, verifies the signature, and enforces it. A fully compromised server can refuse a transaction; it cannot authorize one more.
Every transaction is checked three times: when the server prepares it, on the player's device before signing, and again at broadcast. The player does not have to trust the server — they can see the call matches the published rules.
Recovery — the studio is never the custodian
The private key is generated on the device and encrypted in the OS keystore; it never reaches the server. Recovery runs on a code only the player holds: a 120-bit code → PBKDF2-HMAC-SHA256, 600,000 iterations → AES-256-GCM. The server stores only an opaque blob it cannot decrypt. The player can export the key any time and open it in any wallet — leaving is allowed, which is what makes “non-custodial” checkable instead of a claim.
Retry-proof writes
Pass an Idempotency-Key on any write. A network timeout that makes the game retry returns the same prepared transaction — the player is never charged twice — while the same key with a different body is refused. A pending transaction can be cancelled with one call, freeing its daily caps immediately. Outgoing webhooks are signed with a timestamp and are replay-proof.
await doAction(client, { account, name: 'buy_gems', params, idempotencyKey: orderId }); await client.cancel(prepareId); // give up a pending tx await client.batchRead([{ address, function: 'balanceOf', args: [addr] }, /* …up to 20 */]);
API surface
One binary serves four surfaces, each with its own authentication, and no surface can call another's routes.
| /v1 | The player and the studio backend — a player JWT, or a server API key. Auth, balances, reads, prepare/broadcast, actions, sessions, encrypted backup. |
| /dashboard | The studio — a session cookie. Apps, keys, contracts, policy signing, players, sponsored gas, webhooks, billing. |
| /admin | Us — cookie + admin role. |
| public | Health, status, the OpenAPI spec, the chain catalogue. |
The OpenAPI spec is embedded in the binary and served as JSON — the published docs can never drift from the server. Any EVM chain works: we manage chain IDs, not a whitelist.
Player erasure (GDPR)
One call erases a player: profile, sessions, wallet backups and sign-in codes are deleted; transactions are kept for the financial record but scrubbed of IP and device data. The audit log records only a pseudonymous hash, never re-introducing the erased identity.