Abstract
ARCADE is a mesh of self-hosted servers ("arcades") that serve browser apps, games, a social layer and an exchange over eight proof-of-work and proof-of-stake chains. No arcade holds a user's keys unless the user hands one over for a capped order, and no single arcade is required for anything to keep working. Each arcade is one Python program run beside unmodified coin nodes. It keeps no account a user depends on: identity, names, messages, posts, tokens and inscriptions are all written to public chains, and every arcade reads them back the same way.
This paper describes how ARCADE is built and why: the node's process model, how chains are run locally or borrowed from peers, how identity is anchored on Pepecoin's test network, how arcades find and talk to each other, how inscriptions are indexed, and how each exchange method settles and who, if anyone, holds funds along the way. It closes with the security model, what an operator controls, and the open problems as of the date above.
The software ships as DogecoinArcade, PepecoinArcade and LitecoinArcade. These are faces of one codebase and speak one protocol; this paper calls the whole ARCADE. Facts here are taken from the code on its main branch at commit 110e55c8 and from arcades running today.
Background and motivation
Most blockchain apps put a company's server between the user and the chain. That server holds the account, often the keys, the order book and the app itself; when it goes away, so does the user's access. ARCADE removes that server by making every piece of state either on chain or reproducible from the chain.
Three problems shaped the design:
- Custody by default. Hosted wallets and exchanges hold keys for convenience. Thefts and shutdowns on small-chain marketplaces show what that costs. ARCADE seals keys in the browser and settles trades by transactions both parties sign.
- Small chains lack infrastructure. Pepecoin, Bellscoin and Luckycoin have few explorers, indexers or wallets. A full node plus an inscription index is too heavy for most people. ARCADE lets one operator run a chain and lend it to many others.
- Identity tied to a site. A username on a hosted service is a row in its database. ARCADE writes names to a chain, so any arcade can resolve them and none can take them away.
The result is an arcade anyone can run on a spare computer, that a player can leave at any time with everything they own, and that keeps working when any single arcade, including the first one, is gone.
Design principles
Eight rules recur through the codebase. When a feature breaks one, the code marks the exception, and so does this paper.
- Keys stay with the user. A wallet is generated and sealed in the browser. The arcade stores only the sealed copy and signs nothing on a user's behalf unless the user hands it a bounded authority: a pre-signed lot, or a key for one capped order.
- The chain is the database. Names, messages, posts, tokens and inscriptions are transactions. An arcade's own databases are caches and indexes that can be rebuilt from the chains.
- Two-party settlement. A trade is one transaction both sides sign, or a pair of hash-locked contracts that either both pay or both refund. Anything custodial is opt-in and labelled, and most of it is capped.
- Borrow, then check. An arcade without a chain borrows it from a peer, and checks what it can (proof of work, headers, its own signatures) instead of taking answers on trust. Where it cannot fully check, the paper says so.
- Cheap identity on a test chain. Names, messages and the mesh's own records ride Pepecoin's test network, so social actions cost a fraction of a coin with no market value.
- Unmodified nodes, no root. Coin nodes run as published by their projects, with signatures checked at install. The arcade runs as an ordinary user service.
- Operators are sovereign. Each operator chooses chains, fees, limits, models and look. Some rules that protect users are fixed in code: two-party token and NFT swaps always wait a confirmation, trades never ask for a SIGHASH_NONE signature, and no lock is built without a time-lock check.
- Any arcade can start the next. Each arcade serves its own program and inscription index to new arcades, so the network does not depend on one website.
System architecture
An arcade is one Python program that runs as an ordinary user service beside the coin nodes. A small supervisor holds the listening socket, a single worker does all the work, and a reload swaps workers without dropping a connection. The stack is FastAPI 0.115 on uvicorn 0.34 with Jinja2 templates; the core needs only two libraries, requests and PyNaCl.
Swipe sideways to see the whole diagram.
Process model
- Supervisor (
arcade/web/supervisor.py). Started asarcade-web, it binds the socket itself with a backlog of 2,048, writesweb-supervisor.pid, and starts a worker that inherits the socket's file descriptor. - Reload on SIGHUP. If the supervisor's own source changed, it re-executes itself first. Otherwise it starts a new worker and waits for it to report "warm" (up to 600 s). Then it sends SIGTERM to the old worker, which drains requests for up to 5 s and is killed after 60 s. Connections that arrive meanwhile wait in the socket backlog.
- One worker at a time. A new worker takes the file lock
web.lockbefore it starts any background work, so two workers never write the same databases. - Crash recovery. A crashed worker is restarted after 1 s, doubling to 60 s, with the delay reset after 300 s of health. systemd restarts the supervisor itself (
Restart=always,NoNewPrivileges,PrivateTmp). - Who sends the HUP. The updater after installing a release, Admin's restart button, and
supervisor.reload().
Background work in the worker
After it takes the lock, the worker starts:
| Component | What it does | Cadence |
|---|---|---|
| Block watcher | 23 phases per pass: confirmations, message and ledger scans, release notices, the shop, cross-chain swaps, the trader, faucets, auto-update | Blocks every 5 s, up to 500 per pass; mempool every 1.5 s |
| Ord index runner | Walks or helps scan inscriptions for each chain set to walk or help | Continuous |
| Mesh | Peer links, rooms, chain lending, order sharing, on its own event loop | Pings every 15 s |
| Sites search | Full-text index of on-chain sites, on chains set to host | Continuous, per chain |
| Standing-order filler | Fills pre-signed orders that meet their price | On each matching order |
| LoRa gateway | Bridges Meshtastic handhelds | When enabled |
| Stall watch | Logs every thread's stack when a request runs over 45 s | At most every 120 s |
Web layer
- Size. About 560 routes in the main app, plus the model API, mesh, real-time rooms and LoRa. The largest areas are
/account(191 routes in the main app),/admin(59) and the app runtime/r(55). - Binding. The default is 127.0.0.1:8420. A request counts as public if its Host is a configured public host, it carries an edge header (
cf-ray,x-forwarded-forand similar), its TCP peer isn't loopback, or its Host isn't a local name. Public requests are served from an allowlist of pages and a short list of POSTs. - Machine interfaces.
/rpc/{chain}is an Omni-style JSON-RPC for bots, authenticated by a cookie file (mode 0600)./v1/chat/completions,/v1/imagesand/v1/modelsfollow the common chat API shape./realtimeserves game rooms over server-sent events, and/meshis the WebSocket door for peers.
Storage
Everything lives in ~/.dogecoinarcade (mode 0700). Settings are one JSON file, written atomically. The rest is SQLite, split by purpose so each can be rebuilt or moved alone.
| Files | Holds |
|---|---|
settings.json |
Every operator setting |
<chain>-ledger.sqlite, <chain>-inscriptions.sqlite |
Arcade tokens and NFTs read from each chain |
ord-<chain>.sqlite with .values and .seen |
The inscription index, coin values seen by the walk, and its coin filter |
.snap, .part |
Index snapshots received from peers |
sites-<chain>.sqlite |
Full-text search of on-chain sites |
test.sqlite, test.key |
Messages and the messaging key |
accounts.sqlite, admin.sqlite |
Seats, sessions, sealed wallets, admin sessions |
| swaps, swap-trades, trades, trade-orders, xchain, escrows (each a .sqlite file) | Exchange state |
mesh.key, mesh-peers.json, trade.key, referee.key |
The node's own keys and peer list |
Chain data stays in each coin node's own folder; the arcade never modifies it.
Chains and backends
ARCADE works with six coins on eight networks today, and more can be added by pull request. Pepecoin's test network is the only one an arcade must run itself; every other chain can run locally, be borrowed from a peer, or be switched off (Pepecoin main can be borrowed but not switched off).
Supported networks
| Network | Id | Default RPC port | Activation height | Swaps | Escrow |
|---|---|---|---|---|---|
| Pepecoin test | test |
44873 | 1,496,133 | Test only | Yes |
| Pepecoin main | main |
33873 | 1,208,000 | Yes | Yes |
| Dogecoin | doge |
22555 | 6,415,000 | Yes | Yes |
| Litecoin | ltc |
9332 | 3,197,300 | Yes | Yes |
| Litecoin test | ltctest |
19332 | 4,926,300 | Test only | Yes |
| DigiByte | dgb |
14022 | 24,450,000 | Yes | Yes |
| Luckycoin | lky |
9918 | 1,153,000 | Allowed by code; not announced | Yes |
| Bellscoin | bel |
19918 | 1,289,000 | No | No |
The activation height is the block where the arcade's own records begin on that chain; the ord index walks from the chain's first inscription instead. A local regtest network exists for development. Messaging is restricted in code to Pepecoin test and regtest.
Why Bellscoin can't swap
The swap and escrow scripts need features some chains lack, and the code refuses rather than build an unsafe script. Any chain added later is checked the same way.
- Bellscoin uses P2SH version byte 30, the same as a Dogecoin "D" address, so a script address can't be told apart safely.
Three ways to reach a chain
Each chain has a setting core:<chain> of here, borrow or off, and a setting share:<chain> that defaults to on. With no local node found, the default is to borrow.
- Local node. The arcade reads the node's cookie, or falls back to the RPC user and password in its conf file. It checks the chain name, and the genesis hash where one is configured, before using it. Pepecoin has no genesis hash set yet, so a Dogecoin node on a Pepecoin port would pass the name check.
- Borrowed node. RPC calls travel over the mesh as
crframes to an arcade that shares the chain. A sharer answers a fixed list of read-only calls,sendrawtransaction, two arcade scan calls (arcade_utxoset,arcade_coinscan), and on test chainsgetblocktemplateandsubmitblock. Wallet calls are never served. - Catching up. A local node that is still syncing reads from a sharer until it reaches the tip.
Checking what a peer says
A borrowing arcade checks the blocks it's given. Raw blocks are re-hashed, their merkle roots recomputed, and their proof of work checked against the chain's algorithm (scrypt with N = 1,024, or SHA-256d). DigiByte has no work check yet, because it mixes five algorithms. On that chain a borrowed block rests on the sharer's word. On every chain, answers about which block sits at a height, unspent coins and the mempool also rest on the sharer's word.
Limits on lending
| Limit | Value |
|---|---|
| Bandwidth per borrower | 512 KB/s |
| Calls running at once across all borrowers (4 per borrower) | 2 |
| Calls per batch | 500 |
| Node-to-node block relay, optional | 256 KB/s per link, up to 4 peers per chain |
An arcade that lends a chain receives 20% of the operator fee on swaps and inscriptions its borrowers make on that chain, at least the dust line. Changes to share: apply within a minute; changes to core: apply at the next start.
Identity and accounts
An ARCADE account is a 12-word seed that never leaves the browser unsealed. Every key a user needs is derived from it, and the arcade stores only an encrypted copy, public keys and hashed session tokens.
Key derivation
The browser draws 128 bits from crypto.getRandomValues and encodes them as a BIP39 phrase. The seed is PBKDF2-HMAC-SHA512 over the phrase (2,048 rounds, salt "mnemonic"). Coin keys follow hardened BIP32 paths. The login and messaging keys use the same HMAC-SHA512 chain under purpose 24946', but aren't standard BIP32 keys. The browser code and arcade/seed.py match byte for byte.
| Key | Path | Curve | Used for |
|---|---|---|---|
| Login | m/24946'/1'/0' | Ed25519 | Signing in to any arcade |
| Messaging | m/24946'/0'/0' | X25519 | Encrypted messages |
| Coins, main chains | m/44'/3'/0'/0/i | secp256k1 | Spending real coins |
| Coins, test chains | m/44'/1'/0'/0/i | secp256k1 | Pepecoin test, Litecoin test |
| Swap orders | m/44'/1'/7'/0/i | secp256k1 | One key per delegated order |
Purpose 24946 is 0x6172, "ar". Litecoin test also derives BIP49 and BIP84 addresses.
Sign-up and the sealed wallet
- The user picks a name and a password of at least 12 characters. The browser refuses passwords on public leak lists, using the k-anonymity range API: only the first 5 hex digits of the password's SHA-1 leave the device. If the check can't reach the list, sign-up goes ahead.
- The browser seals the phrase with AES-256-GCM under a key from PBKDF2-HMAC-SHA512 (600,000 iterations, 16-byte salt, 12-byte nonce). The parameters travel with the blob so they can be raised later; the code itself notes Argon2id would be stronger.
- The browser posts the name, public keys, addresses per chain and the sealed blob to
/signup. The arcade refuses a name the chain already gives to another address, a name another arcade holds under a different key (when the chain names no owner, it asks peers for up to 4 s and goes ahead if none answers), or a key that already has an account. - The arcade takes a seat and stores the blob in its vault table:
vault(tag, pubkey, address, blob, made, claimed). The account table holds no password and no name.
The password never reaches the arcade, so the 12-character rule is enforced in the browser. Only the operator's remote admin password is checked server-side (scrypt, N = 2^15, r = 8, p = 1).
Signing in
GET /auth/challengereturns a nonce, good once for 120 s.- The browser signs
DogecoinArcade login\n{origin}\n{nonce}with the login key. The arcade checks that the origin is one of its own names, which stops a lookalike site from replaying the signature elsewhere. - The arcade issues an
arcade_sessioncookie (HttpOnly, SameSite=Lax), stored only as a SHA-256 hash and good for 14 days, renewed by use.
Sign-in is limited to 20 attempts per 5 minutes per IP and per key.
Moving between arcades
If an arcade has no vault entry for a name, it asks up to 16 known arcades over HTTPS for /signin/<name>?here=1 (the flag stops the request being passed on). If the chain names an owner for that @name, only a blob for that address is accepted. The browser warns that the copy came from elsewhere, checks the public key, and files the blob locally. Imported-wallet records follow over the mesh, accepted only when signed by the same login key and newer than the copy held.
Backup and recovery
The Backup page saves the sealed blob as a file; the arcade keeps no extra copy. /restore is the only page that asks for the 12 words. It signs in with the derived login key, reseals under a new password, uploads only the new blob, and ends the account's other sessions.
The real-coin gate
The browser, not the arcade, decides which networks hold real coins: main, doge, ltc, bel, lky and dgb, plus any name it doesn't recognise. Every real-coin signature shows a review card, except within a capped coin allowance that a batch inscription grants for its own tab, for up to 180 minutes. Test chains sign with the coin-type-1 key, so an arcade that lies about a chain's network gets a signature from a key holding no real coins.
@names
Names live on Pepecoin test and are read the same way by every arcade.
- Format. 2 to 24 characters of
a-z,0-9and_, with 20 names reserved (admin,arcade,supportand others). - On chain.
INSC | version 1 | kind | length | name, with kind 3 for a claim and 4 for a transfer, carried in one zero-value OP_RETURN output (bare multisig as a fallback). - Rules. The first claim in chain order wins. Each address holds at most one name and each name one address. A new claim frees the old name. A transfer must come from the holder and is refused if the recipient already has a name.
- Cost. One transaction at 0.01 PEP test per kB; at most 5 claims an hour per account.
The operator and the node
An arcade has two identities. The node has its own keys (mesh.key for the mesh, trade.key, referee.key) and an address on each chain. The operator is one ordinary account, named only from the machine itself. The arcade's displayed @name is the operator account's name if one is set, otherwise the name held by the node's own address.
The mesh
Arcades link over an encrypted, authenticated peer-to-peer protocol of their own, not HTTP. A node's identity is the Ed25519 key it proves in the handshake. Messages between arcades are signed, so a forwarding peer can drop or delay one but can't forge it.
Transport and handshake
The mesh runs on raw TCP, port 8421 by default. Where no public address can be proved, an arcade opens a Cloudflare quick tunnel and speaks the same protocol over a WebSocket at /mesh, which carries only ciphertext. Nodes behind NAT on IPv6 meet with a simultaneous open every 15 s, and STUN runs on UDP 3478.
- HELLO. Each side sends
ARCM | version 2 | network name | X25519 ephemeral key. The network name isarcade-<ledger>, so arcades on different ledgers don't link. - Keys. Each direction gets its own key from BLAKE2b over the shared secret and the transcript, personalised
arcm-dialerorarcm-listener. - Frames. Every frame is sealed with XChaCha20-Poly1305 under a counter nonce. A frame is a 4-byte length and a body of at most 64 KB.
- AUTH. Each side signs
arcade-mesh-auth-v1, its role and the transcript with its long-term mesh key. That key, kept inmesh.key, is the node's id.
Discovery and links
- On-chain announcements. An arcade writes
arcade-mesh <ip> <port> <key>(orarcade-mesh-ws wss://…/mesh <key>) to Pepecoin test every 24 h. Only global IP addresses are accepted, never hostnames, and announcements older than 14 days are ignored. - Its own address. The public IP comes from the local node's
getpeerinfo, and needs at least 2 peers to agree. - Other sources.
--mesh-peeron the command line, and peer exchange. Peers are remembered for 30 days. - Link budget. 6 outbound links and up to 32 inbound. Every 5 minutes the node tries a new peer, and swaps out its slowest outbound link if the newcomer's round trip is at least 20% faster. Links ping every 15 s and are dropped after 45 s of silence.
Rooms
Rooms are publish-subscribe channels that flood across the mesh. A subscription carries 8-byte BLAKE2b fingerprints of room names and is signed by the node.
| Limit | Value |
|---|---|
| Payload per message | 512 bytes |
| Rate per sender | 5 a second, bursts of 10 |
| Hops | 16 |
| Clock skew allowed | 60 s |
Players join a room through their arcade with a certificate signed by their coin key: arcade-realtime\n{network}\n{node}\n{address}\n{expires}, good for a day and checked by the arcade's own node with verifymessage. Anyone else is a guest.
What travels between arcades
| Frame or room | Carries | Trust rule |
|---|---|---|
cr |
Borrowed RPC calls and answers, block and transaction relay | Blocks are re-checked, other answers rest on the sharer; a sharer caught lying is avoided |
arcade-book room |
The shared swap order book: changes every 1.5 s, the full book every 30 s | A node may speak only for its own orders |
ix |
Index fingerprints, bisected to find the first block where two indexes differ | At most 120 questions a minute |
dx |
Inscription index work, leased out in chunks, plus snapshots | Two arcades must agree before a chunk counts |
kc |
Known collections, by fingerprint | Agreement, not authority |
iw |
Imported-wallet records | Signed by the account's login key; newer wins |
wl |
Welcome asks, payments and closings | Signed by each arcade's mesh key |
fr |
Handing a first @name to a new operator | Unsigned offer and accept, trusted through the authenticated link |
op |
Operator name, version, hosts, sites, disk | Shown only; never used for payment or permission |
Messages between people
Private messages are transactions on Pepecoin test, not mesh traffic, so they survive any arcade going offline.
- Contact keys. A user publishes an X25519 key in a key announcement, with optional fields such as @name, picture, bio and link. A contact code reads
arcade:<network>:<base58 key>:<checksum>. - Envelope.
crypto_box_seal(recipient, sender_pk ‖ crypto_box(sender_sk, recipient_pk, header ‖ msg)), 126 bytes of overhead. The recipient can authenticate the sender, and the sender can later deny it. The envelope hides the sender's key, but the message transaction shows the sending wallet and pays the recipient, so who messaged whom is public. - Delivery. A message transaction pays the recipient 0.01 PEP test, so their wallet sees it at once. Web Push sends an empty wake-up, and the page then asks its arcade what's new.
Welcoming a new arcade
A new arcade needs a little Pepecoin test before it can claim a name or announce itself. The welcome protocol arranges that in the open.
- Ask. Once linked, an arcade whose wallet holds less than 1 PEP test gossips a signed
askrecord. Asks are rate-limited to 30 an hour per link. - Send. Any operator can answer by sending 1 PEP test and gossiping a signed
sentrecord with the transaction id. - Close. The new arcade checks each named transaction with its own node. The first block that pays it closes the ask, listing every payment in that block. A closed ask never reopens.
- Score. Each arcade whose payment is in that block gets a point on the Welcomers board. If the coins came another way, such as mining, nobody scores.
The alternative is to mine a test block. The miner builds its own coinbase and, when it borrows the chain, pays the lending arcade a tenth of the reward in the same coinbase. Mining refuses real-coin and merge-mined chains.
Multiplayer
There are no game servers. A room name inside a game is the same room on every arcade, and a player's page picks the fastest arcade and re-checks every 10 minutes.
- Seats. The operator joins as the node, players with their day certificate, everyone else as a guest. A dropped stream keeps its seat for 30 s.
- Fast lane. Games can open WebRTC links: a full mesh of up to 20 players, or a star of up to 60 around a host, at up to 16 KB a message. A pair that can't connect directly falls back to the room, or to the operator's TURN relay for signed-in players.
- Services. A program on the operator's machine can join a room as the node, to keep score, hold shared game state, or referee.
Games, inscriptions and the ord index
Everything a player sees, from a game to a collection, is an inscription read from a chain. ARCADE writes two inscription formats and reads a third, indexes them per chain, and serves inscribed web content inside a strict sandbox.
Three formats, two written
| Format | Chains | How it's carried | Owned by |
|---|---|---|---|
| ARCADE's own | Pepecoin main and test | Omni-style type-200 data with magic INSC, in bare-multisig outputs, about 7,650 bytes per transaction |
An address |
| Doginals-style ord envelope | Dogecoin, Pepecoin main and test, Litecoin, Bellscoin, Luckycoin, DigiByte | 240-byte pushes, up to 1,500 bytes of pushes per reveal transaction | A coin (satoshi) |
| Taproot ord | Litecoin, Litecoin test, Bellscoin | Standard ordinals witness envelope; read, not written | A coin (satoshi) |
Ord inscriptions made through an arcade use postage of 0.001 coin, a maximum of 400 kB, and at most 20 unconfirmed at a time.
The ord index
Ord-style inscriptions belong to whichever coin carries them, so finding who owns what means walking every block from the chain's first inscription and tracking coins through every spend. That is the heaviest work an arcade does.
- Modes per chain:
walk(keep the full index),help(scan chunks for others),read(watch only: read another arcade's index), oroff. The default isreadon every chain. - Shared work. The chain is cut into chunks of 100 blocks. Arcades lease chunks to each other for 600 s, and a chunk counts once 2 arcades agree on its hash.
- Reorgs. The index keeps 300 blocks of undo data and treats blocks as settled after 12 confirmations (2 for the walk itself).
- Coin filter. A Bloom filter of possibly inscribed outpoints (7 hashes, about 12 bits per coin, about 0.3% false positives) lets the walk skip clean coins fast. It's saved beside the index as
.seen. - Values. Coin values the walk has read are kept in
.values, keyed by the first 12 bytes of the txid plus the output index. Only values read from bytes that hash to that txid are stored. - Checking each other. Arcades compare index fingerprints over the mesh and bisect to the first block where they differ.
- Snapshots. A new arcade can download a peer's index as a snapshot rather than walk from scratch.
Collections
A collection is a root inscription (arcade-collection) plus parts of up to 500 items each, written to Pepecoin test. The first publisher of an item owns its entry, and only the same publisher can replace a root. Operators can also import a public list of known collections, off by default, agreed between arcades by fingerprint.
Sending an inscribed coin safely
The wallet keeps inscribed coins out of ordinary payments. Any coin of 0.01 or less is treated as possibly inscribed; coins up to 0.1 are traced back, 600 steps per lookup, before they're spent. To send an ord, the inscribed coin goes in as input 0 and out whole as output 0, with the fee paid only from clean coins. The send is refused unless the inscription sits at offset 0.
Games and apps
A game is a text/html inscription with a JSON card naming it a game or app.
| Field | Limit |
|---|---|
| Name (required) | 60 characters |
| Description | 280 characters |
| Version | 20 characters |
| Players | 20 characters |
| Genre | 24 characters |
| Minimized timeout | 900 s by default, up to 86,400 s |
The creator is the address that inscribed it. The newest card per creator and name wins, so an update is a new inscription by the same creator, and a lookalike by someone else shows as a different game. Lookalike cards are hidden until the operator reviews them.
The sandbox
Inscribed content runs in an iframe without allow-same-origin, served with a content security policy of sandbox allow-scripts allow-pointer-lock, connect-src 'self' and form-action 'none'. Network requests can go only to the arcade itself, such as other inscriptions and the runtime scripts under /r/. Inline and evaluated scripts are allowed inside the sandbox. Each view gets a ticket for one page and one address, good for 12 hours and renewed while in use. Operators can serve content from a separate host name for stronger isolation.
A game reaches the wallet only through the runtime, and only if the player switched on Wallet access for that card. Every real-coin signature still shows a review card, with one narrow exception: the creator allowance lets a game send back to its own creator, without a card, a token they issued or a piece they inscribed, up to 0.05 coin per send within 20 coins per tab. On test chains, prize-pool claims (0.05) and inscribing to one's own address (2 coins) also sign without a card, under the same cap.
Exchange mechanisms
ARCADE has no matching engine and no exchange wallet. Trades on one chain settle as a single transaction both parties sign; trades across chains settle as two hash-locked contracts. Custodial options exist, are opt-in, and are capped.
Pre-signed trades are raw transactions with partial signatures, not PSBTs. The signature-hash code allows SIGHASH_ALL, ALL|ANYONECANPAY (0x81) and SINGLE|ANYONECANPAY (0x83). It refuses NONE, plain SINGLE, and SINGLE with an input index past the last output.
Same-chain trades
| Kind | How it's built | Lifetime | Where the funds sit |
|---|---|---|---|
| Exchange listing (tokens, NFTs) | Seller signs 0x83: input 0, the piece, over the token payload; input 1, a seller coin, over the payment | 24 h by default, up to 90 days, by the arcade's promise; the signature stays valid until its coins move | Seller's own address |
| Ord listing | Seller signs the inscribed coin as input 2, 0x83 over output 2, the price. The buyer adds two padding inputs and funding | 30 days | Seller's own address |
| Bid | Buyer signs a payment 0x81, with every output fixed | Until spent or cancelled | Buyer's own address |
| Shop swap in an app | Test chains only. Seller's arcade offers one of its outputs; the buyer signs, the arcade checks every buyer input, then countersigns | 15 minutes | Each side's own address until the one transaction confirms |
Two rules came out of the October 2026 Doginal Dogs theft, in which 264 inputs signed with SIGHASH_NONE|ANYONECANPAY let an attacker move 201 pieces out of 194 wallets. A listing can only be re-listed cheaper, so an old, cheaper signature can't undercut a newer one. To cancel, the seller spends the coins: deleting a listing on the arcade doesn't revoke the signature.
The shared order book
Swap orders are relayed in the mesh room arcade-book: changes go out on the 1.5 s pass and the full book every 30 s. Each arcade may speak only for its own orders. An order listed from an open browser tab is shown only while the tab pings its arcade every 3 s, and drops out 12 s after the last ping; it costs nothing on chain. Orders can also be inscribed in batches of up to 50 that expire within 30 days.
Cross-chain swaps
Swipe sideways to see the whole diagram.
Each side pays into a P2SH address with this script:
OP_IF
OP_SHA256 <hash> OP_EQUALVERIFY <claimer> OP_CHECKSIG
OP_ELSE
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refunder> OP_CHECKSIG
OP_ENDIF
- Secret. 32 random bytes, hashed with SHA-256. Each hash is used for one swap only, because the script doesn't commit to an amount.
- Lock times. Unix times, never heights. The initiator locks for 8 h, the responder for 4 h, apart by half the lock time and at least an hour. They were 2 h and 1 h until 2026-10-27, and were raised because a block may be stamped up to 2 h ahead of real time.
- Margins. The responder's lock time is set when the swap is planned, 4 h ahead. The initiator may claim only until 2 h 20 min before it, leaving room for clock drift. A planned responder lock time less than 2 h 50 min away is refused.
- The trade watcher. The browser pre-signs both the claim and the refund with
SIGHASH_ALLand hands them to its arcade, which can only add the secret and choose when to send. It sends a refund once the chain's median time passes the lock time, and a claim once the lock has the agreed confirmations. A responder's claim reads the secret from the spend of its own lock. - Decided by the chain. The next step is a pure function of chain facts. Whether a swap is done is judged by which branch of the script was spent, not by where the outputs went.
Confirmations
Speed and safety are traded off per chain. Coin legs below a line act on unconfirmed payments; at or above the line, and on any leg that carries a token, NFT or inscription, they wait one confirmation. In trades the arcade makes for itself, its own real-coin side always waits one. The Pepecoin main swap service is the exception: it acts on unconfirmed deposits.
| Chain | Wait for a confirmation from | Default cap on a delegated order |
|---|---|---|
| Pepecoin main | 1,000,000 PEP | 100,000 PEP |
| Dogecoin | 1,000 DOGE | 1,000 DOGE |
| Litecoin | 2 LTC | 1 LTC |
| DigiByte | 1,000 DGB | 1,000 DGB |
| Bellscoin | 200 BEL | 200 BEL |
| Luckycoin | 20 LKY | 20 LKY |
Operators can move the line (swap_big:<chain>) and the cap (delegate_cap:<chain>). Delegated orders on test chains are never capped, and a swap whose legs are both test-chain coins never waits. The line for Bellscoin is set in code but unused, since that chain can't hold a swap lock.
Trust-based and custodial methods
These are the exceptions to two-party settlement. Each is labelled where it's offered.
- Delegated swap orders ("fill while I'm away"). The browser derives a key for one order and hands it to its arcade, which watches the deposit address and trades for the user. The arcade's operator can spend that deposit. Only the coin side is capped. Withdrawal returns the deposit after any swap in flight settles; orders last up to 30 days.
- Buy orders, away mode. The buyer pre-signs lots, each worth its own price plus fee, with 0x83 over their own change output. The signature doesn't cover the price, so whoever holds it can spend that lot; the arcade keeps only signatures that check out, and cancelling sweeps the lots back. In the other mode nothing is pre-signed and the buyer signs when notified.
- Game escrow. Items sit at
<tag> OP_DROP OP_IF <referee> CHECKSIG OP_ELSE <unlock> CLTV OP_DROP <owner> CHECKSIGOP_ENDIF. Before the unlock time (1 to 720 hours) only the referee can move them; after it, only the owner. The referee signs after a judge replays the game: a JavaScript function run in QuickJS with 5 s of CPU and 64 MB, withDateandMath.randomremoved, so every arcade reaches the same verdict. - Prize pools. A pool's lots are pre-signed 0x83 and sealed with AES-GCM under a SHA-256 key derived from a phrase hidden in the game. Whoever finds the phrase can claim. The pool address comes from the creator's own seed, so the creator can also sweep it at any time. Refereed pools add the referee's signature, binding the prize to the player who played.
- Pepecoin main swap service. Test-chain assets for Pepecoin main, run by the arcade, which holds both deposits during the swap. A fixed 0.5% fee from the seller, a solvency check before every payout, 24 h for a deposit to arrive. It acts on unconfirmed deposits and has no per-order cap. Off unless the operator turns it on.
- Model credit. Paid straight to the model seller, who keeps balances and counts usage.
Who holds what
| Method | Before settlement | During | If it fails |
|---|---|---|---|
| Same-chain trade | Each party | Nobody: one transaction | Nothing moved |
| Cross-chain swap | Each party | Each party's own script lock | Both refund on their time locks |
| Delegated swap order | The listing arcade | The listing arcade | Withdraw at any time; orders stand up to 30 days |
| Buy order, away | Buyer, with signed lots at the arcade | Buyer's lots | Cancel and sweep |
| Escrow | Shared script | Referee | Owner after unlock |
| Prize pool | Pool address | Anyone with the phrase | Creator can sweep it at any time |
| Pepecoin main swap service | The arcade | The arcade | Arcade's solvency check |
| Model credit | The seller | The seller | Seller's word |
Fees and the network's own costs
Every action pays its chain's network fee. On top of that, an operator may charge for trades and inscriptions on main chains. Admin offers each operator fee from 0 to 2% (the code would accept more if set by hand), and the trade fee is off by default.
Network fees
Fees are priced per kilobyte of virtual size: the larger of the byte count and 20 times the signature operations, because a bare-multisig output counts as 20 sigops.
| Chain | Minimum fee per kB | Dust line |
|---|---|---|
| Pepecoin, Dogecoin, Luckycoin | 0.01 | 0.01, plus 0.01 extra for each output below it |
| DigiByte | 0.01 | 0.0001 |
| Litecoin, Bellscoin | 0.0001 | 0.0001 |
A cross-chain lock also carries its own claim fee (a fixed 0.01 on Pepecoin, Dogecoin and Luckycoin; elsewhere the larger of the dust line and 400 bytes at the chain's rate), paid by the locker on top of the amount. Locks holding a token or an inscription carry a small reserve so the claim can move them.
Operator fees
| Fee | Setting | Range | Default | Paid by |
|---|---|---|---|---|
| Trade cut on swaps | trade_cut |
0 to 2% | Off | In swaps, taken inside the lock and paid by the claim, at least the dust line; in shop sales and order fills, added to the price |
| Inscription fee | mainnet_create_fee_permille, inscribe_fee:<chain> |
0 to 2%, with a per-chain minimum | 0.5% | The inscriber, on main chains only, never on test chains |
| Pepecoin main swap service | Fixed | 0.5% | Service off | The seller |
Lending is paid in kind
An arcade that lends a chain receives:
- 20% of the operator fee on swaps and inscriptions made through the borrowing arcade on that chain, at least the dust line;
- 10% of the reward of any test block mined with its work, paid in the same coinbase.
Social costs on the test chain
Names, posts and messages cost a fraction of a Pepecoin test coin, which has no market value. New accounts get 100 PEP test on many arcades (the operator sets the gift and daily top-up). On a main chain the faucet is refused unless the operator explicitly enables it, with a hard cap of 10 coins.
Limits that protect the commons
Hourly quotas per account keep any one user from flooding an arcade or a chain. Operators can raise or lower them up to a ceiling of 100,000 an hour and 50 MB a day.
| Action | Default per hour |
|---|---|
| Claim | 600 |
| React | 60 |
| Post, message, send, list, trade | 30 each |
| Inscribe, issue a token | 10 each |
| Change @name | 5 |
Data is capped at 250,000 bytes a day, and an account may have at most 4 offers waiting at once.
Security model and threat analysis
The design assumes any arcade may be careless or hostile, including the one a user signs up on. For the two-party methods, the mitigations below hold against a hostile arcade. The custodial methods, unproved borrowed reads and the gaps at the end are where a user still has to trust one.
Trust boundaries
| Party | Trusted for | Not trusted for |
|---|---|---|
| The user's browser | Keys, signing, the real-coin gate | Nothing beyond the user's own device |
| The user's arcade | Serving pages, relaying signed transactions, keeping sealed blobs | Holding keys; deciding which network is real; moving coins without a signature |
| A lending arcade | Answering chain reads in time | Correctness on chains with a work check; wallet calls of any kind |
| Other mesh peers | Forwarding | Forging messages; payments or permissions based on what they claim |
| The chains | Final word on names, coins and inscriptions | Nothing more: a reorg can undo recent blocks |
| Referees and judges | Releasing escrow before unlock | Holding items after unlock |
Threats considered
| Threat | Mitigation |
|---|---|
| Arcade steals keys | Phrase sealed in the browser with AES-256-GCM; the arcade sees no password |
| Weak or leaked password | 12-character minimum for new passwords; leak-list check by k-anonymity, which goes ahead if the list can't be reached; 600,000 PBKDF2 rounds |
| Lookalike site replays a login | The signed login message includes the page's origin, checked by the arcade |
| Arcade lies that a chain is a test chain | The browser decides; test chains sign with a key that holds no real coins |
| Arcade substitutes a different trade | The user signs the exact transaction; listings use 0x83 so the price output is fixed |
| Old cheap listing undercuts a new one | Re-listing is allowed only at a lower price; cancelling spends the coins |
SIGHASH_NONE drain, as in the Doginal Dogs theft |
NONE and plain SINGLE are refused for every signature the arcade asks for or stores; stored lot signatures are checked since 2026-10-27 |
| Arcade signs an input it didn't offer | Before countersigning, every buyer input is checked against the arcade's wallet |
| Lying peer for a borrowed chain | Raw blocks re-hashed, merkle roots and proof of work checked; height, unspent-coin and mempool answers aren't proved; a sharer caught lying is avoided |
| Wrong node on a port | Chain name checked before use, and the genesis hash where one is set (not yet for Pepecoin) |
| Block timestamps skewed to steal a swap | 8 h and 4 h locks, 2 h drift allowance, 20 min claim margin, median-time refunds |
| Reused swap secret | Each secret hash is accepted for one swap only |
| Unsafe scripts on weak chains | Swaps and escrow refused where CLTV isn't enforced, P2SH bytes collide, or transactions carry timestamps |
| Spam and flooding | Hourly quotas, daily byte caps, mesh rate limits, seat limits |
| Malicious game | Sandboxed iframe without same origin, strict CSP, per-card wallet switch, review card for real coins |
| Remote takeover of Admin | Operator account plus a 12-character scrypt password; 12 h sessions, SameSite=Strict; a custom header on every write, and a same-origin check whenever the browser sends an Origin; key settings only at the machine |
| Tampered update | Release manifest signed with a pinned Ed25519 key; archive must match the signed hash; downgrades refused (archive installs; git installs aren't verified yet) |
Operating a node
An arcade needs one always-on computer, Python 3.10 or newer, and a local Pepecoin test node. Everything else is optional and set per chain.
Install
curl -O https://dogecoinarcade.com/install.py && python3 install.py
The installer downloads Pepecoin Core and checks it by SHA-256, and by GPG signature when gpg and the signing key are present, creates a virtual environment in ~/.dogecoinarcade/venv, and registers arcade-web as a systemd service (a user unit unless installed system-wide). Coin nodes added later from Admin are checked against pinned SHA-256 hashes. No root access is needed for a user install.
Suggested hardware
The project doesn't publish requirements yet. These figures come from running arcades, measured on 2026-10-30.
| Setup | Runs locally | CPU | Memory | Disk |
|---|---|---|---|---|
| Starter | Pepecoin test; all else borrowed | 2 cores | 4 GB | 32 GB SSD |
| Standard | Pepecoin main and test, Pepecoin ord index | 4 cores | 8 GB | 128 GB SSD |
| Full | Adds Dogecoin, Litecoin, Bellscoin and their indexes | 8 cores | 32 GB | 2 TB, indexes on SSD |
Chain data on that date: Pepecoin test about 2 GB, Pepecoin main 15 GB, Dogecoin 224 GB, Litecoin 255 GB, Bellscoin 411 GB. The arcade itself uses about 0.6 GB of memory; its data folder holds 49 GB on a full arcade, mostly inscription indexes.
Network
| Port | Protocol | Purpose | Needed |
|---|---|---|---|
| 8420 | TCP, HTTP | Player and admin pages, bound to 127.0.0.1 by default | Local only unless published |
| 8421 | TCP | Mesh | Inbound helps; a Cloudflare quick tunnel is the fallback |
| 3478 | UDP | STUN for NAT traversal | Optional |
To publish an arcade, an operator either runs the built-in wizard for a Cloudflare named tunnel (which sets up app. and pages. host names and a user service), forwards a port, or starts with --public.
Configuration surface
| Area | Settings | Changed from |
|---|---|---|
| Identity | Operator account; brand (Dogecoin, Pepecoin or Litecoin face); public and local host names | Machine only, except brand |
| Access | Remote admin password (12+ characters) | Machine only |
| Seats and quotas | seats (1 to 10,000); quota:<action>, quota:bytes |
Admin |
| Chains | core:<chain> (here, borrow, off), share:<chain> |
Admin; core changes apply at the next start |
| Indexes | ord_index:<chain> (walk, help, read, off); ord_cores; ord_share (10 to 100% of the machine); sites_index:<chain> (host, ask, off) |
Admin |
| Trading | trade_cut, swap_big:<chain>, delegate_cap:<chain>, auto_sell, auto_fill, node_trades, xchain:enabled |
Admin |
| Fees | mainnet_create_fee_permille (0 to 20 per mille), inscribe_fee:<chain> |
Admin |
| Faucet | faucet, faucet_daily; real-coin faucet off unless enabled, capped at 10 coins |
Admin |
| Safety | moderation (a local model's URL, name and key); flags:broadcast, flags:follow |
Admin |
| Extras | turn (call relay key, apps, monthly GB); room for sale (boost:*); LoRa gateway; affiliates |
Admin |
| Updates | auto_update, update_channel (live or test) |
Admin |
Command-line flags cover the rest: --home, --port, --host, --public, --brand, the mesh flags (--no-mesh, --mesh-port, --mesh-peer, --mesh-tunnel, --mesh-core and others), and --<chain>-conf, --<chain>-datadir, --no-<chain> for each add-on chain.
Updates
- Channels. "Main live" gets a release after a day without trouble; "Main test" gets every change on the test branch.
- Checks. The watcher checks every 15 minutes. A signed release notice from the project's release account triggers an immediate check.
- Verification. The release manifest
{revision, sha256, published}is signed with Ed25519 and checked against a key pinned in the code. The archive must hash to the signed value, and a downgrade is refused. Verification fails closed. - Switch-over. After installing, the updater sends the supervisor a HUP and the new worker takes over without dropping connections. The update is judged by the revision now installed, not by the exit code.
- Safety stops. An update is skipped while a send is in flight, and on a source checkout.
Monitoring
Admin shows each chain's sync state, disk cost and index progress, plus the Welcomers board and notifications. The stall watcher logs every thread's stack when a request runs over 45 s. Logs go to the systemd journal (journalctl --user -u arcade-web).
Limitations and open problems
ARCADE works today on real chains, but several problems are open. Some are engineering work in progress; others are trade-offs the design accepts.
Engineering work in progress
- Index cost. Walking an ord index is single-threaded per chain and dominated by per-block SQLite work. On the team's own machines, Bellscoin's index held about 81 million inscriptions by block 88,000, and a full walk takes days. Batching commits across blocks gained only about 3% in a trial; the next step is likely a different storage layout.
- Lane ordering. When many lanes trace old inscriptions back at once, the chunk the walk needs next can wait behind others. A fix that finishes the lowest chunk first is under review.
- Verified pages. A player can't yet tell whether an arcade serves the standard, unmodified pages. A plan for signed page manifests, cross-checks between affiliated arcades, and badges is in progress.
- Balances across arcades. An account used on several arcades can show different balances until each arcade has scanned for its coins. A shared per-account coin list is planned.
- Platforms. Linux on x86-64 is tested, and Windows is being tested now. macOS and ARM are expected to work.
- Documentation. Parts of the built-in guide trail the code; corrections are under review.
Accepted trade-offs
- Test-chain identity. Names and messages ride Pepecoin test because it's cheap. A test network can in principle be reset, and its coins have no value that would deter spam beyond the quotas.
- Unconfirmed swaps. Small coin swaps settle fast and risk a double-spend; large ones wait. The line is the operator's choice.
- Custody where it's useful. Delegated orders and the Pepecoin main swap service hold user funds, within caps, because "fill while I'm away" can't work otherwise.
- Seats. Each arcade caps its accounts (50 by default) so one machine isn't overwhelmed; more players means more arcades.
Open questions
- Luckycoin swaps. The code allows them; the guides say they aren't open yet. The project maintainer is to decide.
- Chains without a work check. How to prove DigiByte (five algorithms) answers from a lending arcade.
- Stronger key stretching. When to move sealed wallets from PBKDF2 to Argon2id, and how to migrate existing blobs.
- Signed git updates. Extending manifest verification to installs that update through git.
- Merge mining. Whether operators should mine test chains together with spare computing power, and how to keep it within each host's terms of service.
Glossary and references
Glossary
| Term | Meaning in this paper |
|---|---|
| Arcade | One running instance of the ARCADE program |
| Face | A brand of the same program: DogecoinArcade, PepecoinArcade or LitecoinArcade |
| Operator | The account that owns an arcade, named only at the machine |
| Mesh | The authenticated peer-to-peer network between arcades |
| Core | A coin's reference node software, run unmodified |
| Borrowing | Using another arcade's Core over the mesh |
| Sealed blob | A user's recovery phrase encrypted in the browser |
| @name | A name claimed on Pepecoin test, one per address |
| Ord | An inscription bound to a specific coin, as in Doginals and ordinals |
| Ord index | The per-chain database of which coin carries which inscription |
| HTLC | Hash time-locked contract: pays whoever shows the secret, or refunds after a time |
| CLTV | OP_CHECKLOCKTIMEVERIFY, the opcode that enforces a time lock |
| 0x83 | SINGLE with ANYONECANPAY: signs one input and its matching output only |
| 0x81 | ALL with ANYONECANPAY: signs one input and all outputs |
| Big line | The size at which a coin swap waits for a confirmation |
| Delegated order | A swap order the user's arcade fills while the user is away, holding its deposit |
| Referee | A key that co-signs escrow releases after a judge replays a game |
| Welcome | The protocol by which operators send a new arcade its first test coin |
References
- ARCADE source code: the main branch, commit 110e55c8, is the basis for this paper.
- Built-in documentation in
arcade/web/docs/:how-it-works.md,swaps.md,escrow.md,exchange-safety.md,shared-cores.md,multiplayer.md,prize-pools.md,remote-access.md, and a page per chain. - Key source files:
arcade/web/supervisor.py(process model),arcade/cores.pyandarcade/mesh/coreshare.py(chains),arcade/mesh/(mesh),arcade/htlc.pyandarcade/trade.py(swaps),arcade/escrow.py,arcade/doginal_index.py(ord index),arcade/release.py(update signing). - BIP 39: mnemonic seed phrases.
- BIP 32: hierarchical deterministic keys.
- BIP 65:
OP_CHECKLOCKTIMEVERIFY. - BIP 113: median time past as the lock-time reference.