You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(send): accept .dusk names as recipients - #124
Send accepts a .dusk name on Mainnet and Testnet. One lens query reads the name's moonlight_address (public account) and phoenix_payment_endpoint (shielded address) records.
Under "alice.dusk points to", each address is a card: the title and the address shortened to 6…6, then one line, "Visible on chain", "Private", or "Not enough shielded DUSK · Shield first" (opens Shield) when the spendable shielded balance can't cover the fee cap. The full address is in the tooltip, and screen readers read it in place of the short one.
A name with both addresses makes the two cards a choice. Public is the default, and a pick is kept for that name through edits, re-renders and Back from Review. A name with one address shows its one card, without radio semantics. The balance line under Amount follows the pick.
A shielded record counts only if it is a valid shielded address with nothing around it.
Sending to your own account or shielded address (by name or pasted) shows a short warning.
A name stays with the address it resolved to. If Contacts replaces the recipient, Send and Review show the new address without the name. A contact is always an address: ★ → New contact while a name is typed fills in the address the name points to, or nothing while the name is still being looked up.
Going back to a name while it's still being looked up keeps the newer answer. An older lookup that fails later no longer clears the cards or turns Review off, and a Review click whose lookup a newer one replaced does nothing.
Review shows the Public/Shielded pill with "Visible on chain" or "Private", and the name over the full address.
An unregistered, expired or invalid name, a name with no address, an unsupported network, or a failed lookup shows an inline error, and Review stays disabled.
The account's own name, shown only when the name points back to the account:
Header: the account chip is one line, the name (or the short account), with ▾. It opens the profile switcher, so the separate ▾ button is gone and the network pill is never covered. ⟳ moves next to TOTAL BALANCE.
Profile switcher: each row has two lines, the profile's label and then its name or short account. Its copy button ("Copy public account") copies the full account.
Receive: under the QR, a name field and an address field, one line each with a copy button. On the Shielded tab the name shows only when its phoenix_payment_endpoint is exactly that address. What people reach through the name is behind an ⓘ next to it. The Copy button is gone, and Copy request link moved into Request payment.
Fix: the popup's dashboard ordering CSS also applied to other views, which put Receive's QR and Review's recipient below the rest of the page. It now applies to the dashboard only.
Lookups use @duskdomains/sdk/lens-reader 0.5.4 (JSR) against the Dusk Domains read lens on the wallet's own node. They start after the popup renders, and an answer is reused for a minute.
The lens driver ships in public/drivers/dusk-domains/ and is checked against the sha256 pinned in src/shared/dusk-domains-releases.json. The SDK's fetch only reaches that bundled file and the configured node, so the wallet still loads no remote code. The SDK and descriptors load only on the first lookup, through a dynamic import.
Notes
Contract IDs and driver hashes come from the reviewed Dusk Domains mainnet and testnet releases. A contract admitted later needs an updated descriptors file.
Size: about +320 kB zipped, nearly all of it the lens driver (364 kB unpacked) and the SDK chunk (140 kB).
In the 380 px popup the chip has room for about 11 characters of a name; longer names end in "…", with the full name in the tooltip and the switcher.
docs/PRIVACY.md: looking up a name sends it to the configured node, and showing names sends the profiles' public accounts to it. Opening the profile switcher looks up every profile at once.
npm run test:coverage: 871 tests pass and the thresholds hold. Tests cover the resolver (public, shielded, both, neither, invalid shielded records), the primary-name lookup and the shielded match, the header chip and switcher, the Send cards (one and two, the disabled shielded card, the pick kept per name), the self-send warning, Review, Receive's fields, copy buttons and ⓘ, contacts from a typed name (also mid-lookup), lookups a newer one replaced, and the bundled driver's hash.
npm run build:chrome and npm run build:firefox pass.
The built Chrome extension in Chromium, popup at 380 px on Testnet, with a test wallet whose account has alice.dusk as its primary name:
the header chip shows alice.dusk and opens the switcher;
wallet-both.dusk shows two cards, with shielded disabled (0 shielded DUSK), and Amount stays on the first screen;
tdi.dusk shows one public card, and wallet-shielded.dusk one shielded card with the not-enough line;
Review shows the pill, "Visible on chain", and the name over the full address;
Receive shows both fields on both tabs, and the ⓘ note;
no page errors.
A live shielded transfer could not be run: testnet currently rejects Phoenix transactions ("phoenix is not enabled in the VM").
On Mainnet and Testnet the recipient field takes a .dusk name. The wallet
resolves it to the name's moonlight_address record with the lens reader of
@duskdomains/sdk 0.5.4 and shows the name with the full public account
under the field. The review screen shows both, and the transfer goes to
that account as a public transfer, the same as for a pasted account; the
wallet checks the account as it checks any address. Invalid, unregistered,
expired and recordless names, unsupported networks and failed lookups get
inline copy and keep Review disabled. A name resolved through another node
is looked up again before review.
The SDK and the pinned release descriptors (dusk-domains-releases.json)
load through dynamic imports on the first lookup, so popup.js grows by
about 4 kB. Only the lens data driver ships with the extension, in
public/drivers/dusk-domains (BUNDLED_DRIVER_ROLES), and the SDK checks it
against its pinned sha256. The reader's fetch serves that file, passes
reads to the wallet's own node and refuses everything else, so no remote
code is loaded and only the node is contacted. The manifest and
permissions are unchanged.
…sses
On Mainnet and Testnet the header account chip, each row of the profile
switcher and Receive show a profile's verified primary .dusk name above its
account. The lens reader's verifyPrimary looks it up after the render that
asks for it; an answer is reused for a minute per node and account and stays
on show while it is looked up again, and a failed lookup is not kept and is
tried again on the next render. On Receive the name shows beside the shielded
address only when the name's phoenix_payment_endpoint record is exactly that
address.
A .dusk recipient resolves its moonlight_address and phoenix_payment_endpoint
records in one lens query. The shielded address is the record's UTF-8 text
and counts only as a valid shielded address with nothing around it; the
public account keeps its key checks. A name with only a shielded address
gets a shielded transfer, with the shielded fee and spendable balance, and a
name with both goes to its public account unless the Public account /
Shielded address toggle under it picks the other. A name with neither says
it has no address to send to. Review shows the name above the address used.
Showing the names sends the profiles' public accounts to the configured node
(PRIVACY.md). The SDK chunk grows by 0.5 kB and popup.js by 2.5 kB.
…twork visible
The header keeps the brand and the network pill at their width and lets
the account chip give way. The chip is one line, the profile's verified
.dusk name or its short account, and a ▾, and it opens the profile
switcher in place of the separate ▾ button. A long name ends in "…" and
a cut short account keeps its last four characters; the tooltip has both
in full. The switcher's rows copy each account, which the chip no longer
does, and Refresh moves from the header to the dashboard's balance title.
While the switcher is open, redraws keep the chip it hangs from, so a
click on the chip still closes it. The 380px popup now shows the chip's
line too.
A .dusk name with both addresses shows them as two option cards in a
radio group, each saying which balance pays and what the chain shows,
with the full address of the picked one under them; the Public/Shielded
pill and the balance line follow the pick. The shielded card can't be
picked while the spendable shielded balance doesn't cover 1 Lux plus the
shielded fee cap, and then says "Not enough shielded DUSK. Shield DUSK
first", linking to Shield. A name with only a shielded address says the
same and holds Review. A pick stays with the name it was made for:
retyping that name, Back from Review and redraws keep it, and any other
name starts on the public account.
Send warns when the recipient, a name or a pasted account or address,
is the current profile's own public account or shielded address, and
still allows Review. Review says which balance pays every transfer and
whether it is visible on chain, and Receive says what people reach
through the account's name.
View tests render the real views on a small fake DOM
(src/ui/testing/fakeDom.js). popup.js grows by 4.8 kB (1.8 kB gzip) and
ui.css by 3.5 kB.
Contacts can replace the recipient while the draft still holds the .dusk name it came from. Review and Back to Send now show and reuse the name only while the recipient is the address it resolved to, so a picked or edited contact never shows, or goes back to, another name. A name lookup that ends after the user left Send, or after a redraw replaced the form, no longer opens Review.
…eceive fields
Wherever a .dusk name and an address show together, the name leads at
full contrast and the address follows smaller and muted. A shortened
account or address is 6…6 in the header chip, the profile switcher,
Receive and Send's option cards, through one helper, with the full one
in its tooltip and, for screen readers, in place of the short one.
Each profile switcher row has two lines: the profile's label, then its
verified .dusk name, or its short account until it has one. The row's
copy button, "Copy public account", still copies the full account.
Receive shows up to two one-line fields under the QR in place of the
address chip, each with a copy button at the right: the name, on the
shielded tab only when its phoenix_payment_endpoint record is that
address, and the short address. The name copies as the name and the
address copies in full. What people reach through the name moves behind
an ⓘ beside it, which stays open across redraws; on the shielded tab it
says people who pay the name choose public or shielded, and anyone can
see the address belongs to it. The Copy account and Copy address button
goes; Copy request link moves into Request payment and turns on once
the QR holds a request. The ⓘ and the copy buttons, the profile
switcher's too, show inline SVG icons centered in the button, rather
than glyphs that sit off-center or show as empty boxes depending on
the system's fonts.
Send says less about a name. Under "<name> points to", with the name at
full contrast, each of its addresses gets an option card: the title and
short address, then "Visible on chain" or "Private", and no balance,
which the line under Amount gives. A name with one address shows its one
card picked, and as no radio, since there is nothing to choose. The full
address under the cards and the Public/Shielded pill go; the pill stays
for a pasted account or address. Where the shielded balance can't pay,
the shielded card says "Not enough shielded DUSK · Shield first", the
link opening Shield as before. Review's line under the pill is "Visible
on chain" or "Private", and Review shows the name over the full address
it resolved to, the address muted.
The popup's dashboard order (balance, actions, tabs, boxes) now applies
to the dashboard's own sections only. Unscoped, it also moved other
views' tabs and boxes to the end, so Receive showed its QR last and
Review showed the recipient below Confirm.
Contacts opened from Send's ★ while the recipient is a resolved .dusk
name fills a new contact's address with the address the name sends to,
the picked card's when it has both, rather than the name, which a
contact can't hold and Save refused. The contact form shows that address
in full before saving and the label stays the user's. As with Save to
Contacts on Review, the contact keeps that address whatever the name's
records say later. A pasted account, or a name that didn't resolve, is
passed on as typed.
… Review's line to the recipient
A profile's .dusk name is reused for a minute. After that, Receive and
the profile switcher wait for a new lookup rather than show the old
answer meanwhile, so a name that changed or went away is neither shown
nor copied, and a lookup that fails shows no name. Only the header's
account chip, which shows the name but never copies or sends it, keeps
the last answer while the new one comes in.
Send keeps the public or shielded pick of each name with both addresses,
by the name as resolved, so a pick for one name no longer drops the pick
made for another.
Review takes public or shielded from its recipient, as its pill does,
for the line under the pill, the default fee and the transfer itself.
Save to Contacts can change the recipient after a shielded pick; Review
then said "Private" under a "Public" pill and sent a shielded transfer
to a public account, which the wallet refused.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Payment-destination resolution introduces deployment and driver trust checks that warrant human validation, and live shielded transfers remain unverified.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Resolve pending name before prefilling new contact address
src/ui/popup/views/send.js:637
If ★ is pressed while a valid name is still being looked up, shownName is null and Contacts → New contact receives the literal name instead of an address. A later successful lookup does not update that prefill, so Contacts' address-only Save validation rejects it. Resolve the pending name before setting the prefill, with guards against navigation or recipient changes during the wait, or prevent name-based contact creation until resolution completes; then use the selected public/shielded address.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The funds-routing integration needs human validation of its SDK and deployed-contract trust boundary, alongside resolution of the remaining lookup race.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Ignore stale lookup completions after input changes
src/ui/popup/views/send.js:492
Switching from alice.dusk to bob.dusk and back while lookups are pending can start a second Alice request, and both completions pass the text-only guard. If the newer request succeeds before the older one fails, the old failure removes the resolved cards and disables Review. Track the active lookup's identity or generation across input updates and the Review continuation, ignore superseded completions, and add an out-of-order regression test.
Going from a .dusk name to another and back while the first lookup runs
starts a second lookup of it, and both answers passed the check that the
field still holds the name. When the newer one answered first, a later
failure of the older one replaced the name's cards with "Couldn't look
up" and turned Review off. Send now numbers the lookups the field starts,
as the user types and on Review, and takes only the latest one's answer,
so a Review click whose lookup a newer one replaced no longer opens
Review either.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The new SDK and lens integration determines mainnet payment destinations and warrants human validation, especially while live shielded transfers remain unverified.
No open issues 🎉
Review effort: Balanced
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
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.
Closes #125
Summary
.duskname on Mainnet and Testnet. One lens query reads the name'smoonlight_address(public account) andphoenix_payment_endpoint(shielded address) records.phoenix_payment_endpointis exactly that address. What people reach through the name is behind an ⓘ next to it. The Copy button is gone, and Copy request link moved into Request payment.@duskdomains/sdk/lens-reader0.5.4 (JSR) against the Dusk Domains read lens on the wallet's own node. They start after the popup renders, and an answer is reused for a minute.public/drivers/dusk-domains/and is checked against the sha256 pinned insrc/shared/dusk-domains-releases.json. The SDK'sfetchonly reaches that bundled file and the configured node, so the wallet still loads no remote code. The SDK and descriptors load only on the first lookup, through a dynamic import.Notes
docs/PRIVACY.md: looking up a name sends it to the configured node, and showing names sends the profiles' public accounts to it. Opening the profile switcher looks up every profile at once..duskname and show it in approval pop-ups #127 (the profile's name for sites and in the approval pop-ups, with Show the connected profile's.duskname connect#56).Validation
npm run test:coverage: 871 tests pass and the thresholds hold. Tests cover the resolver (public, shielded, both, neither, invalid shielded records), the primary-name lookup and the shielded match, the header chip and switcher, the Send cards (one and two, the disabled shielded card, the pick kept per name), the self-send warning, Review, Receive's fields, copy buttons and ⓘ, contacts from a typed name (also mid-lookup), lookups a newer one replaced, and the bundled driver's hash.npm run build:chromeandnpm run build:firefoxpass.alice.duskas its primary name:alice.duskand opens the switcher;wallet-both.duskshows two cards, with shielded disabled (0 shielded DUSK), and Amount stays on the first screen;tdi.duskshows one public card, andwallet-shielded.duskone shielded card with the not-enough line;