← docs
DogecoinArcade — what it does
Everything the application can do, what each thing costs, and what it
deliberately does not do. Written against the code; where a number appears it
was measured rather than estimated.
The shape of it
DogecoinArcade is a sidecar: it runs beside Pepecoin Core (or Dogecoin
Core) on your own machine and reads the chain the node already has. It never
holds your keys — the node's wallet does — and it never asks for a passphrase.
Everything it knows, it learned by replaying blocks, which means two
installations reading the same chain agree without talking to each other.
|
|
| Interface |
http://127.0.0.1:8420, bound to loopback because it can spend |
| Chains |
Pepecoin and Dogecoin, mainnet and testnet |
| Messaging |
testnet only, by design (D-010) |
| Tokens, inscriptions, @tags |
mainnet and testnet |
| Storage |
your node's datadir for the chain; ~/.dogecoinarcade for what the app remembers |
Messages
Private messages between two people, encrypted end to end and carried on the
chain.
- Read from the mempool, not only from blocks. A message is a
transaction, so waiting for a block meant waiting a block; the scanner
reads what is on its way as well, and a message usually appears within
seconds. It says "in the pool, not in a block yet" until its block lands.
Balances, the order book and inscriptions are still read from blocks and
only from blocks — those are what two nodes must agree about.
- Sealed to one key. Every message is a
crypto_box_seal to the
recipient's X25519 key, with the sender authenticated inside the envelope.
A forged sender fails inner authentication and is recorded as a forgery
rather than shown.
- Your own copy is the only one you will ever have. A sealed message
cannot be read back off the chain by the person who sent it — that is the
encryption doing its job, not a gap — so the plaintext is kept locally when
it is sent.
- Long messages and files are split across several transactions, sealed
once as a whole. A split wallet funds each piece independently so they all
go at once rather than one per block.
- Pictures can be sent Large, Medium or Small. Re-encoded in the browser
before anything is uploaded; each button shows what that size actually
costs. A setting that would come out larger than the original is not
offered.
- Progress is honest. A long send says "36 of 51 transactions in blocks"
and is marked confirmed only when all of it is — which is also when the
recipient can read it.
Costs a transaction fee, and for a file, dust in the outputs that carry it.
Both are shown before anything is sent.
The public board
Channels anybody can read. Nothing here is encrypted and nothing can be
deleted — that is the point of them.
- Posts carry a name, text and optionally a file, in the same chunked carriage
as a private message but unsealed, so any node can reassemble one with no key
and no identity.
- A picture is a post: you can post one with nothing typed.
- No confirmation step on testnet, for a post or a message: the coins are
free and what it cost is reported as it happens. Mainnet still shows the
bill first.
- A board open on another screen refreshes itself when something is
posted, rather than waiting to be reloaded.
- Name an inscription and the board shows a card for it — its number,
name, type and size as this node's index has them, with a button to the
viewer. Never the inscription's own page: a post is written by a stranger,
an inscription can be a page of scripts, and the viewer is the one place
with a sandbox around it. A txid this node has not indexed stays text.
- Channels are just names. There is no step to create one.
- Loads 40 posts at a time with a way back through older ones.
Tokens
An Omni-style token ledger, indexed from the chain.
- Create a token with a fixed supply, or a managed one you can grant and
revoke against later.
- Send tokens to an address, grant and revoke on a managed token,
and hand the issuer role to somebody else.
- Balances, holders and history come from replaying the chain, so every node
agrees. A transaction the engine cannot understand stops the index rather
than being skipped — a silently skipped transaction is how two nodes come to
disagree about who owns what.
- Mainnet and testnet are separate ledgers with their own start blocks.
NFTs
A file written onto the chain in full and uncompressed, owned by an
address. Called an inscription in the code and on the wire, and NFTs in the
interface, because that is the word people arrive with.
- Any file, any size. There is no policy ceiling. The only limit is
arithmetic: the countdown that marks the last piece is two bytes, so 65,536
pieces — about 498 MB.
- An immutable JSON field travels with it, inscribed with the content and
covered by its hash. It cannot be edited afterwards, so it is checked for
validity before it is paid for.
- Owned and transferable. An inscription belongs to an address and moves
only when its owner sends it. Bound to an address rather than to a
satoshi, so spending your coins never moves your inscriptions by accident.
- Numbered in chain order at the moment the last piece lands, so every
node replaying the same chain assigns the same numbers.
- Verified on reassembly against the SHA-256 in its manifest: a missing
piece reads as missing rather than as a broken picture.
What it costs, measured:
| File |
Transactions |
Chain |
Fee |
Dust |
Total |
After sweeping |
| 100 KB |
14 |
205 KB |
2.1 |
18.1 |
20.1 |
2.1 |
| 1 MB |
138 |
2.0 MB |
20.2 |
178.0 |
198.3 |
20.2 |
| 5 MB |
688 |
10.1 MB |
100.9 |
887.5 |
988.4 |
100.9 |
The dust is not lost: every data output is a 1-of-3 including your own key, so
it is spendable again by whoever made the inscription.
What an inscribed page can call
An inscription can be an HTML page, and that page can ask this node questions —
the same idea as ordinals' recursive endpoints, with the same paths where the
meaning matches.
| Endpoint |
Returns |
/content/<id> |
the bytes, with the type its creator gave it |
/r/inscription/<id> |
owner, creator, number, type, length, hash |
/r/metadata/<id> |
the JSON field, parsed |
/r/inscriptions |
a page of them, newest first |
/r/inscriptions/<address> |
what an address owns |
/r/collections |
the collections on the chain, newest first |
/r/collection/<creator>/<name> |
one collection: its items in edition order, and its traits |
/r/blockheight, /r/blocktime |
where the chain is |
/r/balances/<address> |
token balances at any address |
/r/tag/<name>, /r/address/<address> |
who holds a @tag, and the other way |
/r/wallet |
which wallet is looking — can be switched off |
/r/send |
asks the wallet to send coins, tokens or an inscription; the person approves |
/r/tx/<txid> |
whether a transaction is confirmed, and how deep |
/r/storage.js |
arcade.storage: what the wallet remembers for the page, since a sandbox has no localStorage |
/r/node.js |
arcade.node: node-to-node messages sent as the wallet the page runs in, and the replies to them |
/r/swap.js |
arcade.swap: a shop page's listings, and a buy button that ends in one transaction both sides signed |
/r/owner.js |
arcade.owner: a page sending, unasked, from the wallet that created it and holds it |
An inscription is <id> by its number or its creating transaction id.
Why this is safe to serve. Inscribed code is written by strangers and runs
in the browser of a wallet that can spend. It is loaded into a frame with
sandbox and no allow-same-origin, so it has an opaque origin: no cookies, no
access to the page around it, no navigating the top window. These endpoints are
the only URLs in the application that answer a cross-origin request — every
other page says nothing about CORS, so an inscription cannot read them. Content
is served with sandbox in its own policy and nosniff, and a type we will not
render is handed over as a download rather than guessed at.
Collections
A set of inscriptions that belong together — a HashLips build, or anything that
names itself the same way.
- Filed from the chain, not from a form. An inscription whose JSON has a
name like Doge Punks #12 is filed under the collection Doge Punks as
edition 12, by its creator — the shape every HashLips build already writes.
A JSON collection field names the set outright. The rule reads only what
is on the chain, so every node files the same sets identically, and a set
cannot be edited, hijacked or renamed afterwards.
- One set is one creator's. Two addresses inscribing the same name make two
collections, so nobody can slip their own items into somebody else's.
- Ordered by edition, with the traits counted across the set, so a page
shows Blue background — 3 of 5, 60% without anybody having filled that in.
- A wizard inscribes a whole build. Point it at a HashLips
build folder
(or upload the json and images folders from a phone). It reads
_metadata.json, shows every item with its picture and its JSON, prices the
whole run, and asks once. Each item's own HashLips JSON goes on the chain in
its inscription's JSON field, compacted and otherwise untouched.
- Pause, resume, survive a crash. Every transaction is written to disk the
moment the node takes it, before the next is built. Pause stops between
pieces; resume carries on from the next unsent one; a crash or a power cut
restarts the run on the next start, and pieces the node took but the
computer never wrote down are found again in the chain index rather than
paid for twice.
- Funded in batches. The wallet is split into outputs sized for the
largest of the next 120 items, so each piece has its own confirmed coin and
a thousand-item run does not wait a block per transaction.
- Messages still go through while a run is on: the send lock is held one
item at a time.
- It can sell itself. Tick the mintpad at the pricing step, name a price
in coins or in a token, and the run inscribes a mintpad when the last item
is on its way — a page that shows the collection and hands over a random
one for that price, to anyone who opens it in a wallet. Last, and only if
nothing failed: a pad for a collection half of which never went up would be
selling things that do not exist. Untick it and nothing extra is inscribed.
Shops and swaps
A shop is an inscription. Its JSON lists what it sells and where its owner's
node listens; the page inscribed with it is the storefront.
- The terms are on the chain.
give and take per listing — coins, a
token amount, an inscription, or a random item of the owner's collection
that the shop still holds, which is what a minting event is. Both wallets
read them from their own ledgers; nobody's page is trusted about a price.
- One transaction or nothing. A swap is a Class C payload naming both
legs, in a transaction both parties sign. The engine moves both legs when
the block lands or refuses the whole thing: a buyer who routes the
seller's own coins back has paid nothing and gets nothing.
- A sale reserves what it sold until its block is indexed. Between
broadcast and indexing the ledger still names the seller as the owner of
what was just sold, so without this a second buyer is offered the same
piece — refused by the engine when it lands, but only after they have paid
a message fee to be told no.
- The seller is not asked. The shopkeeper answers every order from the
block watcher's thread: checks it still holds the item, checks the buyer
holds the price, locks one of its outputs and sends an offer; then checks
the buyer's half-signed transaction against exactly that offer before it
signs and broadcasts. It cannot sell what the JSON does not list, sell it
for less, or sell from a shop this wallet did not create and hold.
- It answers orders, not answers. An answer carries the txid it answers
and whether it worked; an order carries neither. A shopkeeper that could
not tell them apart would answer the other shop's refusals for ever, a
message a block out of each wallet, which is what two of these nodes did
for eleven minutes before it was fixed.
- The buyer is asked, once. The offer arrives as a node-to-node message,
the buyer's wallet builds the transaction and shows it in the approvals
pop-up like any other send, and Approve signs the buyer's half only.
- Any kind for any kind: tokens for coins, a random NFT for tokens, one
inscription for another. Send the shop inscription away and the shop is
closed.
- A page of your own may send without asking. A page this wallet created
and holds is the owner's own words:
arcade.owner.send builds, signs and
broadcasts at once, and the send is listed with the approvals as one that
came from a page of your own.
Testnet only, like the messages it travels on (D-010). Swaps are read from a
starting height per chain (swaps_from), so a swap in a block before it is
recorded as unread rather than as an old transaction meaning something new.
The exchange
Everything for sale on a chain, read from the chain itself. Four tabs, four
questions.
- Offers — what somebody has offered for an NFT of yours, and what you
have offered for somebody else's. Accept or refuse; accepting builds the
seller's half of one transaction and the buyer's wallet signs the other.
- Mintpads — every mintpad with pieces left. A pad appears because it is
on the chain, not because anybody listed it, and disappears when it mints
out.
- Tokens — every token paired against the chain's coin, with its last
price and the day's move. Click a pair for its candle chart, its order
book — asks and bids, best price first — and the form that puts an order
on it. An order rests on the chain until it is cancelled.
- An ask holds its tokens back, so the book cannot show what the seller
has since spent. A bid holds nothing: no covenant on this chain can
reserve coins and still let a wallet spend, so a bid is an intent and the
page says so.
- Nothing is matched by the engine. A fill is a swap — one transaction
carrying both legs, which is the only way a coin leg and a token leg move
together. The book is what is on offer; the swap is how it settles.
- NFTs — single NFTs for sale, part of a collection or not. An NFT
that has moved drops off by itself: the listing is read from the chain
every time, so there is nothing to un-list.
Charts of what actually traded. Tokens and NFTs both get candles, built
from the swaps on the chain — there is no order book to draw, so what is
drawn is what people paid. A day with no trade is a dot on the axis, never a
line ruled to the next price. One chart per collection, and one currency
to a chart: what a Goofball goes for says nothing about what a Doge Punk
goes for, and coins and tokens on one axis is adding pounds to metres. Drawn
as SVG in the page: no charting library is fetched from anywhere, and the
page's CSP would stop it if it tried.
An offer is made with what you have. The form lists the tokens this
wallet holds with their balances, and the coin balance beside the coins
option; it does not draw a token picker when the wallet holds none. The door
checks again when the form comes back. Offering something you do not hold is
a fee spent to be told no by somebody else, a block later, for something
your own wallet could see at once.
A wallet is told before it pays. A buyer's side of a swap is two
transactions — the half they sign and the message that carries it — so it
needs two confirmed outputs. A wallet with one is refused before the order
goes out, not three transactions later, and told to split the address.
An offer can be made on any NFT, listed or not: open one you do not own
and press Make offer. It is said on the chain, not sent to anybody:
somebody holding an NFT never asked to be reachable, and most have published
no key at all, so the holder's own wallet finds the offer by watching its own
things — any address, no announcement needed. It fits one OP_RETURN, so it
costs a flat fee and locks nothing.
An offer already accepted says so, and says who it is waiting for and until
when, rather than offering to accept it again.
It is not a PSBT — the buyer cannot build the transaction, because the
seller's own output has to be its first input. The holder's Accept builds
the seller's half and sends it to the buyer, whose wallet signs its half
automatically only if it names the same item and the same price that was
offered. Which is why making an offer needs your key published: you are the
one asking for an answer. An offer on something of yours is never answered
without you.
Approvals
A page or a program can ask this wallet to send something. It cannot send.
- One queue for everything that has value: coins, tokens,
inscriptions and the buyer's half of a swap, asked for by an inscribed
page in its sandbox (
POST /r/send, arcade.swap) or by a program on the
bot RPC (da_requestsend, da_requesttoken, da_requestinscription).
- Nothing is built on the caller's word. A request is a row that says
what, to whom and why. When you look at it, the transaction is built and
shown — fee, every output, the change coming back — and goes out only when
you press Approve and send. Refusing costs nothing.
- Shown everywhere you are: in a pop-up in front of the inscription that
asked, the moment it asks; a banner on every other page; and on the phone
over the remote tunnel. Only a request made while that page is open pops up
over it; what was already waiting is listed under the page with a link, not
opened over a page that never asked. The page that asked cannot see the
pop-up or press anything in it, and it is told afterwards whether the
transaction is confirmed, and how deep.
- What can be refused at once is refused at once: an address on the other
chain, a token that does not exist, an inscription that is not yours to
give. A request nobody answers within an hour expires; no more than twenty
wait at once.
- What was never asked is still written down. A page of your own and a
shop of your own may send without asking — they are your own words and your
own listing. Both are written to the same queue the moment they go, already
decided, so the one page a person reads shows everything this wallet signed
for something that is not a person. A shop's line reads from the shop's
side: "sold 100 Arcade Test for 1.00000000 coins".
- The caller's own words — what it calls itself, why it asks — are shown in
quotes as exactly that.
A handle that belongs to an address. @martin.
- One name to an address, one address to a name. A name pointing at two
people is not a name.
- First claim wins, in chain order — the only ordering every node already
agrees on.
- Changing your tag frees the old one for anybody else to take. Holding
names you no longer use is how a namespace fills with nothing.
- Transferable to another address, by its holder, and refused rather than
dropped on an address that already holds one.
- Lower case,
a-z 0-9 _, 2 to 24 characters. No hyphens (too easily confused
with the several dashes that are not hyphens), no dots (they read as domains),
and a short reserved list for names a reader would take as official.
A claim is 13 bytes, so it fits an OP_RETURN: naming yourself costs one small
transaction. Claim or change one from the address book, in two steps like
a send: the transaction is shown before anything is broadcast, and a name
that is malformed, reserved, already yours or already somebody's is refused
before it costs anything.
- Shown wherever this wallet says who somebody is: the messenger's
conversation list and header, and the address book. A name you typed wins —
it is yours — then the @tag, then the address. A stale or missing index
shows the address rather than a name it cannot vouch for.
- Published with your key. An announcement carries your key, your address
on this chain, the same wallet's address on the other chain, and your @tag.
The tag is the part a reader can check: their own index says who holds the
name, and an announcement that disagrees is shown as disagreeing rather than
believed. The name you type is no longer published at all — it travels with
a first message, and stays yours to give people in your own book.
The address book
Who is who, and where to pay them. Stored only on this computer — not
published, not on any chain, and not visible to anyone you message.
- A contact holds a name, a testnet address, a mainnet address, a contact code
and your own notes. The contact code is their identity and can only be set
when the entry is created.
- Names found by scanning are kept apart from names you typed. A name you
wrote always wins over one read off the chain, because a published name is a
claim rather than proof.
- Introduce myself sends your @tag and your addresses with a first message
to somebody new, so they know who is writing. Untick it to stay anonymous.
There is no name to type: your @tag is the only name this wallet gives out
for you, because it is the only one a reader can check.
- Scan for published addresses lists people who have published a messaging
key on the chain and are not already in your book. Publishing is optional, so
plenty of people will never appear there.
Remote
Reach the wallet from your phone, from anywhere, for a while.
Press Open the tunnel, choose 4 hours, 12 hours or a day, and scan the QR
code. No account, no port forwarding, nothing to change on your router.
What protects it — not the random hostname, which is a URL and therefore
not a secret:
|
|
| A key |
The QR carries a key as well as the address. The address alone opens a locked page. |
| A deadline |
It closes itself. A door left open by accident is the failure this exists to prevent. |
| No bot RPC |
/rpc/* is refused through the tunnel outright. It has its own key and it can spend. |
| A door for the pages |
An inscribed page runs in a sandbox that can carry no cookie, so it gets a second hostname of its own, opened alongside and never shown: it serves only the page's content and the page API (/content/*, /r/*), and answers 404 to everything else. |
It tells a phone from this machine by Cloudflare's own headers, not by address —
cloudflared runs on your machine and connects to 127.0.0.1, so a phone in
another country arrives from localhost.
The bot RPC
A JSON-RPC endpoint for programs: airdrop bots, faucets, leaderboards, anything
that reads balances or moves tokens without a person clicking.
- Speaks Omni Core's method names (
omni_getbalance, omni_send,
omni_listproperties…), so anyone who has scripted against Omni knows it.
- Cookie authentication like bitcoind:
~/.dogecoinarcade/rpc.cookie,
mode 0600, rewritten at every start. A browser tab cannot present it.
- Sending is two calls.
omni_send* builds, funds and signs but returns the
transaction unsent with its fee; omni_broadcast sends exactly those
bytes. A bot sees the fee before it pays it.
arcade-rpc is the shell client; examples/airdrop.py is a working bot.
See Bot RPC for the full method list.
Node-to-node messages
An encrypted channel for one arcade to talk to another — for marketplaces, bots
and anything built on top.
- The same sealed envelope as a private message, so the same guarantees:
addressed to one key, authenticated as from yours.
- Its own envelope type, so machine traffic never lands in a human's
conversation, and its own cursor, so a program can read a queue and resume
exactly where it stopped.
- Every message says what API it speaks: a protocol number and four bytes
over the API surface itself, inside the ciphertext where it cannot be edited
in flight. Two nodes on different builds that speak the same API are
compatible, and mismatch is reported rather than enforced.
- Methods:
da_identity, da_send, da_broadcast, da_inbox, da_markread.
- An inscribed page can use it too:
arcade.node sends as the wallet the
page runs in and reads only the answers to what that page sent. No key of
its own, no approval — a message moves nothing and lives on testnet — and
thirty an hour per page, so a page in a loop is a nuisance and not a cost.
On the chain rather than peer-to-peer because a custom P2P message does not
propagate — Pepecoin tolerates an unknown command and drops it without
forwarding. The mempool already gossips to every node in seconds.
Wallets, keys and backup
- One address a chain, holding the coins, the tokens and the NFTs. Every
awkward thing a wallet of many addresses does came from the alternative: a
token stranded on a receiving address with no coins to move it, an NFT on
one address and the published key on another, a balance in three piles
needing three transactions. Change comes back to the sender everywhere.
Address reuse links a wallet's activity together for anyone reading the
chain; that is the price, and this ledger is public anyway.
- What lands elsewhere walks home by itself — a few things every couple
of blocks, coins first, because a token cannot move off an address that
cannot pay its own fee. It waits for anything else that is sending, so it
cannot collide with a collection run part way through, and it never sees
an output held for an open offer. Testnet only: sweeping somebody's
mainnet coins without asking is spending their money for them.
- On a real chain, only the wallet this application made. A node there is
usually somebody's own wallet too, and those coins are not the arcade's to
show, to spend or to gather; what the arcade made is filed under its own
accounts. On a test chain the node exists to run this, so every address in
it is the arcade's and everything is gathered. An address carries its chain
in its version byte, so nothing has to remember to pass a flag.
- Three tabs: Wallet, Tokens, NFTs. The wallet answers what you have —
coins per chain, token balances with the form that sends them, and the
inscriptions you hold with a Send on each. The Tokens and NFTs sections
answer the other question, what exists on the chain and how to make more.
Each points at the other.
- One balance a token, not one an address. A wallet holding a token on
four addresses holds one balance of it; the addresses are behind "where it
sits". A send comes out of one address, so the wallet picks the smallest
pile that can cover it — spending a small pile up rather than breaking a
large one — preferring one that can also pay its own fee.
- More than one pile is more than one transaction, not a refusal. When no
single pile covers the amount, the wallet uses the largest first and takes
the remainder from the smallest pile that covers it. All of them are shown
before any is broadcast, and one yes sends the set.
- One wallet per chain, with a two-step send: the decoded transaction and its
fee are shown before anything is broadcast.
- Change comes back to the address that paid, so the messaging identity is
not quietly emptied by an ordinary send.
- Split for fast sending cuts the wallet into many small outputs, so the
pieces of a long message fund themselves independently and go at once.
- Fees are what the miner counts. The node prices a transaction by its
virtual size — bytes, or twenty per signature operation, whichever is
more — and a bare multisig output counts twenty. Every funding call goes
through
arcade/fees.py, which counts the sigops of what it just built
and raises the fee rate until the block assembler would take it at
0.01 PEP/kB; a plain send is unchanged, an inscription chunk pays about
0.004 PEP per data output. Before this, testnet mined one piece per
block.
- Mine a testnet block from the wallet page, any time. The node hashes
in batches of about thirty seconds, the page says how many hashes so far
and how long a block takes at today's difficulty, and Stop mining stops
after the batch it is on. Testnet only, because mainnet has miners.
- Your wallet is your identity. There is no passphrase and nothing to write
down: back up
wallet.dat and everything comes back with it — coins,
messages and your published key.
- Publishing your key lives on the address book, beside the name it
publishes. There is no page of keys and fingerprints: a fingerprint is
plumbing, and showing one asks a person to check something they have no way
to check.
What it does not do
- No custom peer-to-peer protocol. See above: it would not propagate.
- No encrypted messaging on mainnet. Testnet only, deliberately (D-010).
- No send-to-owners (Omni type 3). The engine does not implement it and a
transaction of that type would stop the index.
- No automatic matching. There is an order book, but the engine does not
cross it: with the chain's own coin on one side, somebody has to sign, so a
fill is a swap between two wallets rather than a match made by the rules.
- It cannot read your sent messages back off the chain. Nothing can. That
is what sealing to the recipient means.