Repository navigation
Modern and DGen wallet looks, Simple mode, contact cards and email receipts (DSM Amendment A17), v0.1.0-beta.4 - #1119
Merged
Merged
Conversation
… Amendment A17) What the Simple wallet shows for a person, and the receipt it can email. Display and courtesy only: no validity check reads any of it. Contact details: - ContactProfileV1 holds a name, email, phone, and the lookup key of a linked phone contact. - A contact's details live in its row's metadata. - contacts.list answers them; contacts.addManual takes them; contacts.setProfile replaces them. Your own card: - Kept in settings and read and set through contacts.ownProfile and contacts.setOwnProfile. - No card yet is answered as an own_profile state with no value. - The contact code carries the card's name (preferred_alias, empty until now) and its new email and phone fields. Email receipts: - receipts.email builds a ReceiptEmailRequestV1 from the contact's email and the owner's name. - The device's AK signs H(DSM/receipt-email ‖ the request with an empty signature). - The request is posted to the env config's receipt_service_url. A network with none says so. - New domain tag DSM/receipt-email; the tag count is 397. Tests (database-backed): - details are kept with the row, replaced whole, and refused for no contact or a bad email; - the owner's card reads back with no phone key, and the contact code carries it; - a receipt request names the contact's email, is signed by the device, and stops verifying when redirected; - no email means no receipt. Mutation: building the contact code without the card makes its test red.
…r the skin For the Simple wallet (DSM Amendment A17): - pickPhoneContact opens the system contact picker after asking for READ_CONTACTS (declared, and asked only then). It reads only the picked contact: name, first phone, first email and lookup key. - The pick reaches the page as a PHONE_CONTACT_PICKED host event, with no bytes when nothing was picked or the read was refused. - phoneContactPhoto reads a linked contact's photo by lookup key, for display only. - shareText opens the share sheet with the user's own contact code. - setSystemBars colours the status and navigation bars light or dark for the skin in use. The legacy bar painting moves into one helper shared with onCreate.
Owner rulings 2026-10-09. A second skin for people new to Web3, drawn on the same data and the same sends. The skin: - Chosen once when the wallet is first ready (default Simple) and switchable in Settings, from either skin. - `html[data-skin="simple"]` puts the device chrome away and the app fills the screen, light or dark. - The phone's bars follow through setSystemBars, asked only on a change. - Choices are bridge preferences: ui_skin, ui_scheme, simple_offline, receipts_email. Tabs (Wallet / People / Activity / Settings): - Wallet: the protocol currency's balance; for a new wallet, its welcome claim instead of a made-up zero. Send, Receive, People, recent payments. - People: contacts named by their details, Send beside each, and adding someone by scanning their code, then picking them from the phone's contacts or keeping the card's name. - A contact's page: edit details, or link a phone contact. - Activity: payments only, filtered by direction. - Settings: the look, offline payments, email receipts behind a consent page that states what is sent, your card, the lock, and Report a problem. Pages are browser history entries, so the phone's back button closes them. What Simple leaves out: - A navigation guard refuses sofi, accounts, tokens, vault, storage, dev_policy, recovery_pipeline and bluetooth while Simple is the skin. - Activity lists no SoFi, token or vault rows. - The offline switch shows only when the owner turns it on, and still says Under construction. One send path for both skins: domain/sendTransfer. The Classic send screen uses it, and online sends now return the transfer's hash, which receipts name. After a sent payment, Simple emails a receipt only when receipts are on and the person has an email. Tests (no test doubles): - what Simple shows and leaves out; - the welcome claim with no zero; - People to a contact and back through history; - Activity filters; - the offline switch; - the receipt promise only with consent and an email; - the route guard in both skins; - the skin choice asked once, after the preferences are read. Mutations: a guard that lets sofi through, and an offline switch shown regardless, are each red. Frontend 915/915, lint, tsc and the real-code guard clean.
…ice signed DSM Amendment A17, outside the protocol. New crate crates/dsm-receipt-service (axum, lettre over SMTP). POST /v1/receipt takes a ReceiptEmailRequestV1. The service: - refuses it unless it is canonical, every field is within its bound and free of control characters, the email is an address, and the sender AK's signature over H(DSM/receipt-email ‖ the request unsigned) verifies; - holds each device and each recipient to a rate over a window, in memory; - emails one fixed text and HTML receipt, with the HTML escaped; - answers where it went, masked (j…@example.com). It keeps no record of who was emailed and logs no address. Open, stated in the README: the signature proves a device asked, not that the device is in the network's directory. Configured by environment (bind, SMTP URL, from, rates), behind an HTTPS proxy; Dockerfile and README beside it. Sending needs a domain with SPF and DKIM and SMTP credentials from the owner. Tests: - end to end over HTTP and a real SMTP exchange with a listener that keeps each message: a signed request is emailed once and answered masked; unsigned, wrongly signed and redirected requests send nothing; a device over its rate is refused and nothing more goes out; - unit tests: the template escapes markup, addresses are masked, a recipient's rate clears after the window, and fields are checked. Mutation: skipping the signature check makes the unsigned and redirected test red.
The tour walks the Game Boy's menus and buttons, which the Simple skin does not have. It runs while Classic is the skin in use, and is offered once Classic is the owner's choice, so it never shows beside the skin choice.
…l receipts Owner rulings 2026-10-09. - The explainer gains A17. - MASTER gains MR-DSM-0322..0325 (DSM 56, 1,038 in all), with the explainer re-pinned. - CONFORMANCE gains §6.83 with its §8 rows. - The matrix gains two rows naming the tests and mutations. - ci/conformance_evidence.py knows the new crate, dsm_receipt_service. The requirements: - details are display only; - the contact code may carry the card's fields and establishes nothing; - a receipt is asked for only with consent and an email, is signed by the device, and is sent only within the rates; - the service decides nothing and keeps no record. Open, as recorded: the service does not read the device directory, and no service is deployed until the owner names a sending domain and SMTP credentials.
… its surface Seen in a render of the Simple screens at phone width: an SVG with no size filled the People card, pushing its text out, and a card row lost its background to the row style in dark.
…ad without a default make lint (Rust 1.98): rustfmt wrapped one line differently from 1.96, and clippy's map_or_identity flagged the status mapping. An out-of-range status is now answered as a 500 naming the error, not mapped to 400.
…s before anything Owner, 2026-10-09: the choice should come before anything else, and setup should not switch from the Game Boy to the Simple skin partway through. The choice: - It is read as soon as the bridge is up, not when an identity is ready: the preferences are the app's and hold before any wallet exists. - It is asked first, over whatever is on screen: a first launch's intro or setup, or a wallet set up before the choice existed. - The page keeps the chosen skin as a hint, so the first paint after a restart is already in it; the preference stays the authority. Simple draws every phase itself (SimpleBoot): - starting up; - a welcome with "Create my wallet" and "Restore from my recovery ring"; - the recovery phrase (SimplePhrase, the same store and checks as the Game Boy's); - publishing, securing (as progress), the PIN lock, and errors; - the ready wallet. The Game Boy's intro waits for its A button, so Simple shows no intro. A wallet locked with a button combo is entered on the Game Boy's buttons, so its lock screen still shows the device. Recovery stays reachable in Simple. Tests: the skin of every phase, the welcome screen's two ways in, securing as progress, and the phrase read, checked and a wrong pick caught. Frontend 919/919.
…picks the colours too Owner, 2026-10-09: the Simple-styled panel laid over the Game Boy's intro and setup showed two looks at once. - A phone that has not chosen sees only SkinChoiceScreen, on the Game Boy every phone starts in: SIMPLE · LIGHT, SIMPLE · DARK or CLASSIC. - It is touch and D-pad driven, and A cannot fall through to INITIALIZE. - Picking sets the colours, then the look, so the whole app switches at once. - The intro plays only once Classic is the choice. A first launch waits on the loading screen until the choice is shown, with no cutscene before it. - The overlay panel is deleted. Tests: the choice is all a phone with none sees (no intro, no INITIALIZE), and each option sets the look and its colours.
The intro plays once Classic is the owner's choice (the previous commit). These three tests are about the Game Boy's intro, so their runtime store says Classic is chosen. I pushed that commit with these three red; this makes the suite whole again: frontend 920/920.
…read On the phones the choice screen reused setup's brick menu, which fits one short word: the three labels sat small at the top of each brick, half covered, and the screen looked like setup's INITIALIZE with its buttons swapped. It is now its own titled screen (ScreenFrame, 'Choose Your Wallet'): a line saying the choice comes before setup and can be changed later, then a full-width button for each look (Simple · Light, Simple · Dark, Classic) with a line under it saying what it is. Touch and the D-pad work as before. Frontend 920/920.
…ook picker The second skin is Modern, not Simple: it reaches everything the DGen Game Boy does (tokens and the faucet, trading, the Bitcoin bridge, storage, apps, scanning, every setting), opening the DGen screens inside itself. Simple mode is a Modern-only switch that puts trading, the Bitcoin bridge, storage and the developer tools away; tokens, people and apps stay. - The look picker is a small fixed black box over the app: Modern or DGen, Dark mode and Simple mode switches, a note on DGen's SELECT colours, OK. The app behind it is drawn in the look picked; nothing is kept until OK. - The Modern wallet lists every token held (created ones included), sends any of them, and has a grid for the rest of DSM. - styles/modernKit.css draws the shared DGen parts (screens, cards, buttons, fields, tabs, every pop-up and dialog) in Modern while Modern is the skin, so pop-ups added later are Modern without a second copy. - DGen gets the contact card (on My QR), the phone-contact pick when adding someone, and email receipts (Settings permission, receipt after a send). - The version shown is the APK's own versionName, read from the Android build.
…hsia in both schemes; still token marks - Light and dark now share the four colours: fuchsia for Send, the active tab, selected segments and highlights; yellow-gold for Receive, the balance card, chips, the current step and money received; black and white beneath. Light had no gold and dark no fuchsia. - The Modern skin draws no spinning coins: a token is its policy icon or a still letter badge in fuchsia or gold (FlatTokenMark). TokenMark, TokenCoin and the screens that drew the ERA and dBTC GIFs directly (CoinArt) all follow; the DGen Game Boy keeps its coins.
… animated scene A cue (sent, sealed, not sent, ready, …) in the Modern skin is a plain sheet: its title, the amount, its words and OK, closing on its own after a moment unless it reports a failure. No scene, no replay. The DGen Game Boy keeps its animations. The two share how a cue closes and takes focus.
…e code, wherever you are added Receive showed the QR with only the code's last sixteen characters, and My Card and Add Contact showed neither, so nobody could add you from Modern. ModernMyCode is the QR, the whole code written out to select and paste, and Share and Copy; Receive, My Card (read again after the card is saved, since the code carries it) and Add Contact all show it.
… discs, Game Boy traces removed - Token badges drew an empty circle: the letter was sized against the badge itself, which a container query cannot do. The letter now sits in its own element, sized by the badge. - A coin that only decorated a card (the faucet, adding a contact) is left out in Modern, disc and all (HeroCoin); still marks stay where they name a token. - DGen screens inside Modern: Back sits in the title row, the D-pad's dashed cursor is gone, Refresh and Inbox get Modern glyphs, the "i" badge and the small labels take Modern sizes. - Button and title text the Game Boy capitalises in CSS is written in sentence case (Claim faucet, Confirm, Version, Developer options, Authentication required), so DGen looks the same and Modern stops shouting.
…rm dialogs and the toast lose their Game Boy parts - Review Send no longer sits grey without a reason: tapped while something is missing it says what (who, a token, an amount above zero), and lights once nothing is. A comma is taken as the decimal point, and one point kept. - The under-construction, confirm and incoming-transfer dialogs drew their message in a green dashed Game Boy box under a green rule inside the Modern sheet. Every part of them (message, info rows, rules, status card, progress steps, spinner, button hover) is Modern while Modern is the skin, as is the toast at the bottom of the screen, whose colours are set inline.
…ed by who the wallet is - My Card: tap the banner or the photo circle to choose a picture from the phone; each is cropped to its shape (banner 3:1, photo square), kept as a small JPEG, and can be removed. They stay on this phone as app preferences: a contact code is a QR and carries only the name, email and phone. - The Wallet tab opens on the owner's banner, photo and name (tap for your card) instead of "DSM Wallet" over a "Wallet" title; no banner is a gold to fuchsia gradient, no photo the name's first letter. The top bar's card button on the other tabs shows the photo too. - Modern only; the DGen Game Boy is unchanged.
…follow the look on screen - A created token's artwork is a logo shape (dsm:coin:v1 mask), not a picture: Modern drew it raw or fell back to the DGen coin. The badge now draws that shape in white on its fuchsia or gold; a linked picture is shown as itself; a token with neither shows its letter. - Behind the first-run picker the app previews a look before one is kept; token marks, coin art, cue pop-ups and decorative coins asked only for the kept look, so a Modern preview drew Game Boy coins. They follow the look on screen (skinShown). - The picker's D-pad ring follows a tap, so it is never on one look while another is picked.
…r; Tokens / Objects switch in Modern - The first screen of a phone that has not accepted it: the beta agreement. Seven points (experimental software; no real-world value; never send real Bitcoin or anything of value; resets; the recovery phrase is the only way back; no advice; as is, at own risk), each ticked on its own; "I understand and agree" works once all are. Kept under its version, so new points ask everyone again. It comes before the choice of look and the intro, in its own plain look. Plain-language acknowledgements, for counsel to review before a public release. - A chosen photo or banner opens a framing screen: drag to move it, pinch, the slider or a mouse wheel to zoom; the picture always covers the frame (a circle for the photo, a 3:1 band for the banner); Save keeps exactly what the frame shows. The picked file is held only while framing. - The Modern wallet's token list has the DGen wallet's switch: fungible tokens, or the one-of-a-kind objects (creatures, items).
…saving the card is a pop-up and back to the wallet - From Android 15 the page ran under the status bar and the navigation buttons, and the bars stayed dark: Modern's tab bar and the agreement's last lines sat under the buttons, with a grey band over Modern light. setSystemBars takes `light` / `dark` (Modern, and the agreement page) or `device` (the DGen Game Boy): Modern is kept between the bars by their own heights, the bars in the page's colour (white, or near-black); the Game Boy runs edge to edge under dark bars as before. - Saving My Card no longer rebuilds the page under the owner (the code was read again and a status line came and went): it says "Saved" in a pop-up and goes back to the wallet. A new photo or banner is kept quietly, the header showing it; only a failure is said.
…which the phone already keeps below the bar The WebView still reports the camera cut-out as a top inset after the page is placed below the status bar, so Modern and the agreement page left a second, empty band above the banner.
On a contact's page, tap their picture to choose a photo and frame it (move and zoom, as for your own); Remove photo takes it away. It is kept on this phone, like your own photo (a contact code carries no picture), and shows wherever the contact does: People, Send, Activity and their page. A photo set here comes before the linked phone contact's photo, which comes before the initial.
…ey scrim behind them - As at the top, the WebView still reports the button area after the page is placed above it, so Modern's tab bar, sheets, framing screen and the agreement left an empty band over the buttons. The phone's own placement is the only inset now. - Behind three-button navigation Android 15+ lays a grey contrast scrim: Modern light showed a grey strip under its white page. The scrim is off while Modern (or the agreement) shows; the Game Boy keeps it.
…ontacts screen does Pairing runs on its own (Rust's loop, while the app is open with Bluetooth on and permitted), but Modern never showed it: no reading the list again while open, no "looking for your contacts nearby", nothing on each contact. People now reads the list on opening, every five seconds and on every pairing status event; shows where pairing has got (looking nearby / connected, exchanging identity); and names each contact's pairing (Paired, Pairing…) with the Bluetooth mark. A contact's page says it in full. The words are shared with DGen's contacts screen (domain/pairing.ts).
…hrough a binary string The bridge contract gate refuses binary-string adapters on bridge payloads; phoneContactPhoto built one to base64 the photo bytes. FileReader encodes the bytes as a data URL directly.
# Conflicts: # dsm_client/deterministic_state_machine/dsm/src/common/domain_tags/mod.rs # specs/requirements/MASTER_REQUIREMENTS.md
…status test counts reads only - A claim whose release held the reserve cell's leader link, before the chain had three links, returned "retry with the same bytes" to its caller (a wallet's first claim could fail so; on CI the two-device fixtures' funding panicked on it). The claim now continues that chain along the remaining route (storage spec §9 rule 8), as the walk does for any release it finds there, and walks again; the next attempt finds the release final and keeps it as the claim's. Both continuations pause a moment, longer each attempt, so the members carry the chain on before the next walk. - a_collects_status_is_read_once_and_answered_from_its_record asserted that a poll reads nothing again, but counted every request a member received, index appends (POSTs) included: an append another step landed during the poll failed it on main's CI. It counts cell and index GETs, the reads.
The Android app already ships as 0.1.0-beta.4; the Rust crates, their path dependencies and lockfiles, and the frontend package said beta.3.
The receipt service and the SDK's email receipts sign and verify with SPHINCS+, and a test moved by one line; the Lean 4 proofs job checks this inventory is current.
The beta.4 version bump renamed every rust-analyzer symbol (dsm 0.1.0-beta.3 -> 0.1.0-beta.4), moving all 631 pins. Each symbol move was checked to differ by the crate version alone; the evidence passed in the full board (2987 passed) and the storage-node run (91 passed) at a0f8245.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two looks for the wallet on the same data and sends: Modern (new; flat, black/white/gold/fuchsia, light and dark) and DGen (the Game Boy). Both are full-featured. Simple mode is a Modern-only switch that keeps things clean for people new to Web3. Also contact cards linked to phone contacts, photos, opt-in email receipts (DSM Amendment A17), a beta agreement, and the 0.1.0-beta.4 version. Owner rulings 2026-10-09.
First run
beta_agreement=beta-agreement-2026-10-09.Modern
html[data-skin="modern"].DGen
Contacts and your card (A17)
ContactProfileV1(name, email, phone, phone lookup key) is held in the contact row.contacts.setProfilesets it, andcontacts.listandcontacts.addManualcarry it.contacts.ownProfileandcontacts.setOwnProfile. The contact code carries your name, email and phone.READ_CONTACTSasked only when used; it reads only the picked contact. The photo is read by lookup key for display only.Email receipts (A17, outside the protocol)
receipts.emailbuilds the request, the device's AK signs it underDSM/receipt-email, and the SDK posts it to the env config'sreceipt_service_url.crates/dsm-receipt-service:App-wide
setSystemBarstakeslight | dark | device.versionNameat build time. Crates, lockfiles and the frontend are bumped to0.1.0-beta.4.domain/sendTransfer. Online sends return the transfer's hash.CI fixes riding along
FileReader, with nobtoa/fromCharCode.Not in this PR
Verification
cargo test --locked --workspace --exclude dsm_storage_node --release. At a0f8245: 2987 passed, 0 failed (33 binaries). The storage nodecargo test -p dsm_storage_node --releasegives 91 passed, 0 failed.make lintis clean. The frontend suite passes. The receipt service passes 7/7, including an end-to-end run over a real SMTP exchange.ci/conformance_evidence.py(MR-DSM-0322..0325, §6.83, matrix rows) and the clock gate pass.