Newest first. Each entry: what was decided, by whom, why, and what it invalidates.
Decided by: Martin.
"Cancel the light coin install. Let's work on Pepe coin but not inscriptions like Doge's. Inscriptions like using Omni. I think there are better features with Omni like the check sum validator and built-in token exchange"
Action taken: Litecoin Core install cancelled and fully removed (binaries, datadir, staged tarballs). Nothing was left on disk; 416 GiB free, same as before we started.
What this invalidates: - Phase 2 as originally written (recovering Martin's 2021-2023 Omni inscriptions from Litecoin) is suspended, not impossible. See "Recovery is not lost" below. - Phase 3's plan to validate our consensus engine against a live OmniLite reference node.
What survives unchanged: Phase 1 Omni research. The Omni protocol sources (OmniLite, Omni Core, the spec) remain the ground truth regardless of which chain we deploy on.
Superseded in part by D-002. Machine survey remains valid.
Verdict: yes. Omni is a meta-layer, not a chain feature — it is a payload encoding plus off-chain consensus rules, interpreted by an indexer. It needs no forked node, only a node whose transaction policy will relay the payload-carrying outputs.
All of the following are verified in source code, from
github.com/pepecoinppc/pepecoin at tag v1.1.0 (commit 4fb5a0cd, 2024-12-16),
cloned to /home/martin/reference/pepecoin.
| Requirement | Pepecoin v1.1.0 | Source | Verdict |
|---|---|---|---|
Class C — OP_RETURN data output |
MAX_OP_RETURN_RELAY = 83 bytes (80 usable payload) |
src/script/standard.h:30 |
✅ identical to BTC/LTC |
| Data carrier relayed by default | DEFAULT_ACCEPT_DATACARRIER = true |
src/script/standard.h:16 |
✅ |
| Data size tunable | -datacarrier, -datacarriersize |
src/init.cpp:483-484,1094-1095 |
✅ |
| Class B — bare multisig relayed | DEFAULT_PERMIT_BAREMULTISIG = true |
src/validation.h:143 |
✅ |
| Bare multisig standardness | x-of-3 accepted as standard |
src/policy/policy.cpp:41-48 |
✅ exactly what Class B needs (1-of-3) |
| Dust limit (soft) | DEFAULT_DUST_LIMIT = RECOMMENDED_MIN_TX_FEE = 0.01 PEP |
src/policy/policy.h:70, :23 |
✅ cheap |
| Dust limit (hard, standardness) | DEFAULT_HARD_DUST_LIMIT = DUST/10 = 0.001 PEP |
src/policy/policy.h:81 |
✅ tunable via -harddustlimit |
| Recommended min fee | RECOMMENDED_MIN_TX_FEE = COIN/100 = 0.01 PEP |
src/policy/policy.h:23 |
✅ |
| Coin precision | COIN = 100000000 (8 decimals) |
src/amount.h:18 |
✅ same as BTC/LTC |
Other chain parameters (verified in source code, src/chainparams.cpp):
| Parameter | Value | Line |
|---|---|---|
| Block spacing | 60 seconds (Litecoin is 150 s, Bitcoin 600 s) | :89 |
| Subsidy halving interval | 100,000 blocks | :78 |
| AuxPoW (merged mining) from height | 42,000 | :136 |
| AuxPoW chain ID | 0x003f (63) |
:119 |
| Mainnet P2P port | 33874 | :153 |
| Testnet P2P port | 44874 | :282 |
| Regtest P2P port | 18444 | :392 |
Consequence of 1-minute blocks: inscription chunks confirm ~2.5x faster than on Litecoin and ~10x faster than Bitcoin. Good for multi-chunk file inscription, which is inherently serial across many transactions.
Martin cited "the check sum validator and built-in token exchange" as reasons to prefer Omni. Both features are real, but one does not do what the name suggests.
omni_getcurrentconsensushash hashes the entire Omni ledger (balances, properties,
offers) at a given block, so two independent implementations can prove they agree.
It is an implementation-agreement check. It is not a per-inscription content checksum,
and it will not tell you whether a recovered JPEG is intact.
A content hash is still the right idea — it was already in the Phase 4 v2 spec
("a SHA-256 hash of the content"). That is ours to design; Omni does not supply it.
Label: assumption pending Phase 1 verification against consensushash.cpp.
Omni's DEx (types 20 offer / 22 accept) sells a token for the chain's native coin — so on Pepecoin, token ↔ PEP. That works and transfers cleanly.
Omni Core on Bitcoin additionally has MetaDEx (types 25-28) for token ↔ token trading.
The transaction-type list Martin compiled from OmniLite does not include 25-28, which
suggests OmniLite lacks MetaDEx. If token-to-token trading matters, that capability may have
to be ported from Omni Core rather than OmniLite.
Label: assumption — must be confirmed in Phase 1 against src/omnicore/omnicore.h.
On Litecoin, OmniLite exists. Our engine could be proven correct by replaying real mainnet
history and comparing omni_getcurrentconsensushash against a reference node at every block.
On Pepecoin there is no Omni, so there is no oracle. We would be the first and only implementation, defining truth with no independent way to catch our own consensus bugs. Also: on day one the DEx has no counterparties, and we must define our own genesis parameters (activation height, Exodus-equivalent address, ecosystem IDs).
Mitigation — and it removes the need for any 217 GiB sync: OmniLite ships unit tests
(src/omnicore/test/) and functional tests (test/functional/omni_*.py) that run on
regtest. Those are executable, authoritative test vectors for the Omni rules, and they
need no mainnet chain at all. Building our engine against that suite gives real correctness
validation on this modest hardware. This was already the Phase 3 plan; it now carries more
weight because it is our only oracle.
Martin's 2021-2023 files are still in Litecoin blocks and are not going anywhere. Omni's encoding (Class B / Class C, the payload layouts) is chain-independent, so if the extractor is written chain-agnostically from the start — chain params, genesis height and Exodus address as configuration, not constants — recovering them later costs a Litecoin sync and a config file, not a rewrite.
Design requirement adopted: every component we build takes chain parameters as config. No hard-coded Pepecoin assumptions.
Decided by Martin:
| Question | Decision |
|---|---|
| First feature after the M0-M2 foundation | M3 MetaDEx exchange |
Protocol marker (Class C, replaces Omni's omni) |
pepo |
| Crowdsales, types 51/53 | In scope |
Marker note: pepo is consensus-critical. It can change freely before launch; after the
first mainnet inscription it cannot change without splitting state.
Verified in source code, omnicore/src/omnicore/tx.cpp:1643-1666 (logicMath_MetaDExTrade):
| Rule | Code | Consequence |
|---|---|---|
Both sides are property IDs (u32) |
payload layout, createpayload.cpp:486 |
The native coin is not a property. MetaDEx cannot quote PEP. |
| Non-fungible properties rejected | isPropertyNonFungible(...) → PKT_ERROR_TOKENS -27, :1653 |
NFTs cannot trade on MetaDEx at all |
property != desired_property |
:1658 → PKT_ERROR_METADEX -29 |
no self-pairs |
| Both sides same ecosystem | isTestEcosystemProperty(...), :1666 |
main and test books never mix |
| Type must be permitted for that property at that block | IsTransactionTypeAllowed(...), :1643 |
activation rules are per-property |
A TOKEN/PEP market with real bids and asks is not possible on MetaDEx. Token ↔ native coin is DEx only (types 20/22), which is one-sided sell offers — an offers list, not a book.
This is not an oversight in Omni; it is how Omni is structured. Omni's answer was a base
property: property 1 (OMNI) is a token, so every pair TOKEN/OMNI is a genuine two-sided
MetaDEx book, while OMNI/BTC is handled by the one-sided DEx.
For us the same shape applies: we need a base property — call it property 1 — against which all MetaDEx pairs quote. Which raises the question Omni answered in 2013 with the Exodus crowdsale: how does property 1 come into existence and get distributed?
Options: 1. Crowdsale (type 51). Send PEP to the issuer during a window, receive base tokens. This is literally what Omni did, and crowdsales are now in scope (D-003). 2. Burn-to-mint. Send PEP to the Exodus-equivalent address, receive base tokens at a fixed ratio. Simple, permissionless, ongoing, no issuer trust. 3. Fixed issuance (type 50) to a treasury address, distributed manually. Simplest to build, most centralised, weakest story. 4. No base property. Accept that only DEx (one-sided, token↔PEP) exists. Much weaker than what Martin asked for.
Second open problem: NFTs cannot trade on MetaDEx (:1653). Selling an NFT therefore needs
either the DEx path or a mechanism we design. Flagged for M4.
Decided by Martin:
| Question | Decision | Consequence |
|---|---|---|
| Base property (property 1) origin | Burn-to-mint | Send PEP to the Exodus-equivalent address, receive base tokens at a fixed ratio, permanently open. No issuer trust, no deadline, no premine. Gives the base token a hard PEP cost floor. |
| NFT sales | Extend DEx (20/22) to accept non-fungible ranges | Deliberate, documented divergence from Omni Core, which rejects NFTs on MetaDEx (tx.cpp:1653) and has no NFT DEx path. Must be spec'd and tested as our own rule. |
| Activation height | Set at launch, ~tip + 1 day | Left unset in config until M7. Indexer backlog stays at zero through development. |
| Ecosystems | Keep both (main + test) | Mirrors Omni ID ranges; gives an on-mainnet sandbox that never mixes with the main book. |
Decided by Martin:
| Question | Decision |
|---|---|
| Project name | Ribbit |
| Class C marker | rbit (0x72 0x62 0x69 0x74) — supersedes pepo from D-003 |
| Base token | 1 PEP = 1 RBIT, divisible (8 dp), uncapped, minted by burn-to-mint only. No premine. |
| Signing | Node wallet RPC — app builds payload, pepecoind funds and signs via fundrawtransaction / signrawtransaction. Keys never enter our process. |
| Litecoin recovery (old Phase 2) | Dropped. |
This is a real simplification, not just a deletion:
', extra/missing =), leave the spec entirely. Inscriptions are v2 only.| Question | Decision |
|---|---|
| Spec and code | Public from the start |
| Web UI binding | 127.0.0.1 only — reach remotely via SSH port-forward |
| Base token ticker | RBIT |
| Delegates (73/74) | In scope |
| Freezing (71/72/185/186) | In scope |
| Send-to-owners (3) | In scope |
| Restricted send (2) | Out of scope |
Why public matters more than it looks: a second independent implementation is the only genuine oracle for consensus correctness, and that can only exist if the spec is public. This partially answers the "no reference implementation" risk from D-002 — not immediately, but it is the only path that ever resolves it.
Why localhost-only matters: the app can trigger signing through the node wallet (D-005), so exposing the UI exposes spending authority. Localhost is the correct default and costs nothing, since SSH port-forwarding covers remote use.
Decided by Martin: no base token at all. Supersedes D-004 and D-005 on this point.
Martin asked: "If the Pepe is burned when the rbit is created, how would somebody convert their RBIT back to Pepe?"
They cannot. Ribbit is a sidecar meta-layer with no consensus power over PEP — only a private-key holder can move PEP. Burning means sending to an address with no key, so the protocol can never release it. This is structural. No design gives trustless redemption of the base coin; any redemption requires a custodian holding real PEP. (Omni itself was custodial here: its Exodus address held real BTC controlled by the foundation, not burned.)
D-005 recorded that burn-to-mint gives RBIT "a hard floor of 1 burned PEP." That was wrong, and backwards. Under uncapped 1:1 minting:
A quote asset is precisely what traders hold between trades, so this would have taxed every user of the order book. Dropping it is the right call.
| Removed | Effect |
|---|---|
| RBIT the token, property 1 | no base/quote asset ships with the protocol |
| Burn-to-mint | the Exodus address reverts to being purely a Class B marker, as in Omni |
| Exodus dual-meaning rule | the "marker vs value sink" collision problem (D-004) disappears entirely |
| One of two unreferenced divergences | only NFT-on-DEx remains as novel consensus code |
Net: meaningfully less consensus surface, and less of it unvalidated. "Ribbit" and the rbit
marker are unaffected — they name the protocol, not a token.
| Pair | Mechanism | Book shape |
|---|---|---|
| token ↔ token | MetaDEx (25-28) | genuine two-sided book, bids and asks |
| token ↔ PEP | DEx (20/22), payment window | one-sided sell offers |
| NFT ↔ PEP | DEx, extended for ranges (D-004) | one-sided listings |
No canonical quote asset ships. If the market converges on one — a wrapped-PEP or a stablecoin issued by a participant — it becomes the de-facto quote asset without us blessing it. That is a defensible position: we provide the venue, not the money.
OMNI_PROPERTY_MSC / OMNI_PROPERTY_TMSC
(verified in source, omnicore.h:123-124). With no base token these are free. Decision:
leave 1 and 2 permanently reserved and unassigned, user properties start at 3. Costs
nothing and keeps a slot if a base token is ever revisited.Decided by Martin: remove types 51 (create property variable / crowdsale) and 53 (close crowdsale) from scope. Supersedes D-003.
Rationale: a crowdsale's payload carries propertyIdDesired (verified in source,
createpayload.cpp:228-239) — it can only be funded with another property, never the
native coin. With no base token (D-007), there is nothing to fund the first crowdsale with.
Keeping them would have meant either shipping a feature that cannot be used, or adding a
second novel consensus rule (native-coin crowdsale funding) immediately after deleting one.
Removed: bonus math, deadlines, early-close rules, issuer-percentage handling, and the crowdsale-participation code path in state processing. A meaningful slice of M6.
Consensus-hash note: GetConsensusHash() has a crowdsale section
(verified in source, consensushash.cpp:232-234). We keep the section in our implementation
but it will always be empty, so it contributes nothing. Documented so that re-adding crowdsales
later does not change the hash of existing state.
Reversible? Yes, cleanly. Crowdsales touch property creation and balances but nothing else depends on them.
Decided by Martin.
| Question | Decision |
|---|---|
| Where it lives | Inside Ribbit — a Ribbit message type sharing the payload codec, Class B/C encoding, indexer, reorg handling and database |
| Primary carrier | Class B (7,646 bytes/tx), Class C for messages fitting in 72 bytes |
| Messages > 7,646 bytes | Chained Class B with a countdown index, using the Doginals UTXO-chaining mechanism |
I had cited Pepecoin's 25-ancestor mempool limit as an argument against OP_RETURN
chunking. Martin pointed out this is wrong, and he is right: the limits are on
in-mempool ancestors (init.cpp:437, :439), and removeForBlock
(txmempool.h:554) drops mined transactions from the mempool, so a confirmation
resets the count. Send 25, wait one block (~60 s), send 25 more. It is a
throughput constraint, not a ceiling.
The real arguments for Class B are atomicity and cost:
Verified in source, validation.h:76,80:
DEFAULT_ANCESTOR_SIZE_LIMIT = DEFAULT_DESCENDANT_SIZE_LIMIT = 101 kB.
An unconfirmed chain is capped at ~101 kB regardless of transaction count. Irrelevant on the OP_RETURN path (25 × ~250 B ≈ 6 kB, so the count binds first), but decisive for chained Class B: at ~14.7 kB per full transaction only ~6 can chain unconfirmed, not 25. Martin's batch-and-wait strategy applies exactly here.
Read from Martin's own github.com/martinseeger2002/doginals, doginals.js:434-560.
MAX_CHUNK_LEN = 240 // bytes per data push
MAX_PAYLOAD_LEN = 1500 // bytes per transaction
'ord', chunk
count, content type, then (index, chunk) pairs where the index counts
down: parts.length - n - 1, so the final chunk carries index 0.<pubkey> OP_CHECKSIGVERIFY <OP_DROP × n> OP_TRUE.OP_DROPs discard.Verified in source, pepecoin/src/policy/policy.cpp:78-93:
scriptSig.size() > 1650 -> "scriptsig-size", non-standard
!scriptSig.IsPushOnly() -> "scriptsig-not-pushonly"
plus MAX_SCRIPT_ELEMENT_SIZE = 520 (script.h:22). So ~1500 bytes of data, a
~72-byte signature and a ~40-byte redeem script land just under 1650. The
constant is tuned precisely to that ceiling. The technique is mainnet-standard
on Pepecoin.
| Carrier | Bytes/tx | Atomic | Ordering | Mainnet-standard |
|---|---|---|---|---|
| Class C (OP_RETURN) | 72 | ✅ | needs index | ✅ |
| Class B (multisig) | 7,646 | ✅ ≤7,646 | n/a, single tx | ✅ |
| Doginals P2SH | ~1,500 | ❌ | UTXO chain | ✅ |
Decision: borrow the mechanism, not the carrier. Chained Class B with a countdown index gives Doginals' structural ordering and self-describing completion while carrying 5× more data per transaction.
| Message size | Carrier | Chunking machinery |
|---|---|---|
| ≤ 72 bytes | Class C, one transaction | none |
| ≤ 7,646 bytes | Class B, one transaction | none |
| larger | chained Class B, countdown index | chunk header only on this path |
Decided by Martin: "The Messenger part of the application should always use testnet."
This is a permanent product rule, not a testing phase. Encrypted messaging never operates on mainnet.
| Area | Effect |
|---|---|
| Network selection | The messaging code path refuses to run against mainnet, enforced in code rather than by configuration. A mainnet params object passed to a messaging function is an error, not an option. |
| Mainnet standardness | Still designed for. Testnet does not enforce standardness (fRequireStandard = false, chainparams.cpp:309), so a transaction that only works there would be silently non-standard. Keeping to mainnet rules means the format stays honest and portable. |
| Threat model | Testnet chains can be reset or deep-reorged. Messages are not durable. This must be stated plainly to users — it is a property of the medium, not a bug. |
| Coins | Testnet PEP has no value, so fee estimates are informational only. |
| Node | Requires a second node alongside the mainnet Ribbit node. |
Testnet accepts non-standard transactions. Without the discipline of mainnet
limits we would not discover that a format is unrelayable until someone tried it
somewhere real. The regtest standardness check (-acceptnonstdtxn=0) in Step 4
is what actually proves it.
Decided by Martin: "Let's change the name of the application to DogecoinArcade. I know it's for Pepe, but it should also work on Doge in the exact same way."
| Item | Old | New |
|---|---|---|
| Project | Ribbit | DogecoinArcade |
| Class C marker | rbit |
arcd (0x61 0x72 0x63 0x64) |
| Python package | ribbit |
arcade |
| Distribution name | ribbit |
dogecoin-arcade |
| Repo path | ~/ribbit |
~/dogecoin-arcade |
Entries D-001 through D-010 above still say "Ribbit". They are left as written: a decision log that gets retroactively edited is not a record. Read "Ribbit" as "DogecoinArcade" in anything dated before 2026-09-11.
The marker change was free because nothing has launched. After the first mainnet transaction it would be impossible — a marker change splits state.
github.com/martinseeger2002/DogecoinArcade already exists and already contains
encrypted messaging (SendSms.py, DecryptSmsData.py, encrypt_data.py,
callGetPubKey.py). Martin's decision: keep them entirely distinct. This is a
new project that shares a name; nothing is merged and nothing is pushed.
Verified against github.com/dogecoin/dogecoin at 1.14.99, cloned to
/home/martin/reference/dogecoin.
Every constant this protocol depends on is identical between Dogecoin and Pepecoin — same values, and the same source line numbers, because Pepecoin is a Dogecoin fork:
| Constant | Value | Source (identical in both) |
|---|---|---|
MAX_OP_RETURN_RELAY |
83 | script/standard.h:30 |
DEFAULT_ACCEPT_DATACARRIER |
true | script/standard.h:16 |
DEFAULT_PERMIT_BAREMULTISIG |
true | validation.h:143 |
| x-of-3 bare multisig standard | yes | policy/policy.cpp:41 |
COIN |
100000000 | amount.h:18 |
RECOMMENDED_MIN_TX_FEE |
COIN/100 | policy/policy.h:23 |
DEFAULT_DUST_LIMIT |
= min fee | policy/policy.h:70 |
DEFAULT_HARD_DUST_LIMIT |
DUST/10 | policy/policy.h:81 |
| scriptSig standardness limit | 1650 | policy/policy.cpp:86 |
DEFAULT_ANCESTOR_LIMIT / size |
25 / 101 kB | validation.h:74,76 |
| Block spacing | 60 s | chainparams.cpp |
Only chain identity differs:
| Dogecoin | Pepecoin | |
|---|---|---|
| mainnet P2PKH version | 30 (D…) |
56 (P…) |
| mainnet P2SH version | 22 | 22 |
| mainnet P2P / RPC | 22556 / 22555 | 33874 / 33873 |
| testnet P2P / RPC | 44556 / 44555 | 44874 / 44873 |
Because Params already holds exactly these fields, Dogecoin support cost
three new Params instances and zero code changes.
Dogecoin testnet and Pepecoin testnet both use PUBKEY_ADDRESS = 113 and
SCRIPT_ADDRESS = 196. A testnet address cannot tell you which chain it is
for. Never infer the chain from an address; record it explicitly alongside any
key announcement or message. Noted in arcade/config.py beside the params.
require_messaging_network() enforces D-010 in code: messaging functions refuse
any mainnet params object. MESSAGING_NETWORKS is {test, regtest, doge-test,
doge-regtest}.
Read from github.com/martinseeger2002/DogecoinArcade, encrypt_data.py.
Existing scheme: secp256k1 ECDH → HKDF-SHA256 → AES-256-GCM key-wrap → AES-256-GCM data.
| Existing | Brief requires | |
|---|---|---|
| Curve | secp256k1, using wallet keys | X25519, separate from wallet |
| Construction | ECDH + HKDF + AES-GCM keywrap + AES-GCM | libsodium sealed box |
| Envelope overhead | 93 bytes (33 pubkey + 12 IV + 32 wrapped key + 16 tag) | 48 bytes (32 ephemeral pubkey + 16 MAC) |
The existing construction is cryptographically sound — ECDH+HKDF+AES-GCM is a legitimate KEM/DEM. Two reasons the new design still differs:
Decided by Martin: "the page should have both testnet and mainnet on it forever because we will be using testnet for messages and main for tokens and nfts and also pep transactions."
| Chain | Role | Sections |
|---|---|---|
| Testnet | Messenger | Inbox, Compose, Keys |
| Mainnet | Ledger | Tokens, NFTs, Exchange, PEP transactions, Explorer |
This is a permanent split, not a migration path. It supersedes the implicit assumption in the web UI that one network was selected at launch.
Messages are not durable: testnet chains get reset and deep-reorged (D-010), and messages are conversational rather than assets. Tokens, NFTs and PEP balances are assets and belong on a chain nobody resets. Each side gets the chain whose properties match what it holds.
It also means a bug in the messaging code can never touch real value, which is a stronger guarantee than any amount of care in the messaging code itself.
The mainnet node runs with disablewallet=1 (docs/00-node-setup.md), chosen
in Phase 0 because nothing then needed to sign. So mainnet is currently
read-only: it can serve blocks, indexed token state and NFT state, but cannot
send PEP or create a token.
Enabling a mainnet wallet is a deliberate step Martin has to take, because that wallet holds real money. Until then the UI must show mainnet sections as read-only rather than failing obscurely at signing time.
require_messaging_network() continues to refuse mainnet for anything in
arcade.messaging. The dual-chain UI makes it easier to point a messaging
call at the wrong node, not harder, so this matters more now, not less.verify_connected_chain() runs on every connection, so a misconfigured
datadir cannot silently cross the streams.Decision. The messaging identity is derived from a wallet address. There is
no passphrase, no key file, and nothing for a user to write down. wallet.dat
is the only thing they have to keep.
D-009 gave the messaging key its own passphrase, encrypted at rest with Argon2id. That was cryptographically sound and a poor product. The failure mode was absolute: forget the passphrase and every message ever sent to that identity is unreadable, by anyone, forever. Two attempts were made to soften it — generating the passphrase rather than letting the user invent one, then saving it on the machine so it could be looked up later — and the second undoes most of the point of the first. Storing a secret next to the thing it protects is not protection; it is a passphrase-shaped ritual.
Martin's instruction was direct: "I don't want a pass phrase to be saved or shown. The only thing that the user should need is their wallet.dat files."
signature = wallet.signmessage(address, "DogecoinArcade messaging identity v1")
seed = HKDF-SHA256(signature)
identity = X25519 keypair from that seed
signmessage is deterministic — RFC 6979 — so the same address always produces
the same signature and therefore the same identity. Verified on Pepecoin:
three calls, byte-identical.
The address is filed in the wallet under the account arcade-identity. Accounts
live inside wallet.dat, which is what makes a restore self-sufficient: the
application finds the address again without needing its own database.
getaccountaddress returns a fresh address as soon as the current one has
been used. Calling it on each startup would therefore have changed the identity
silently, orphaning every message already received — the exact failure the
passphrase was removed to avoid. Caught in testing, before it shipped.
The rule is now: take the account's existing addresses, sort them, use the
first. getaccountaddress is reached only when the account is genuinely empty.
Three independent installations against one wallet derive the same identity, and
the CLI and the web interface agree with each other.
Whoever can use the wallet can re-derive the identity. The wallet's security becomes the messaging security; if the wallet is unencrypted, so is the identity in practice. Against a standalone key with its own passphrase this is a real reduction, and it is the trade being made deliberately: the passphrase's failure mode was certain and common, this one requires an attacker to already have the wallet — at which point they have the coins too.
A spending key is still never used for encryption. Deriving a separate X25519 key keeps encryption away from spending, so a compromise of one is not automatically a compromise of the other, and the messaging key can be rotated by moving to a different address without moving funds.
vault.py — the credential store that remembered passphrases — is deleted.--key, for identities made
before this. They are never picked up implicitly, because the web interface
derives from the wallet and the two halves of the application answering to
different identities would be worse than either choice alone.arcade/backup.py and the Backup page. If the wallet is the only thing that
matters, keeping it has to be something a person can actually do.Decision. A public, unencrypted group channel runs on both chains. D-010 is unchanged: nothing encrypted ever touches mainnet.
D-010 says the Messenger is testnet-only, permanently, and the reasons were specific: messaging code is young, a bug in it must never be able to touch real value, and an encrypted message carries key material that would tie a mainnet identity to a correspondence. None of that applies to a public post.
A group post is not sealed to anybody. It carries no key material. It reveals nothing that publishing it does not already reveal, because publishing it is the act. So the question "may this run on mainnet?" stops being a safety question and becomes a cost question, which is the user's to answer. Martin answered it.
MessageSender still calls require_messaging_network() on construction. The
only way past it is public_only=True, which is:
main
and doge-main.Class C: one OP_RETURN output. One output means no dust, so a post costs a fee and nothing else. That caps a post at about 60 characters.
That is a real limit and the right trade. Class B would carry 7.5 KB, but it pays roughly one unspendable output per 30 bytes — on testnet that is free coins, and on mainnet it is somebody's money burnt to say something in public. A 60-character public post costs about 0.0028 in fees; the same text carried Class B would cost that plus several coins of dust forever.
The rest of the application trains the expectation that messages are private, so the public half has to contradict that expectation loudly rather than quietly:
A channel is a name. Anyone using the same name is in the same room; there is no invitation, no member list, no way to remove anyone, and no moderation. Saying so plainly is the whole of the access-control design, and pretending otherwise would be worse than the limitation.