Token UX fixes: enforce declared supply, honest supply display, faucet source, atomic control - #291
Merged
Merged
Conversation
…elds
Three issues an operator hits on a freshly created instrument:
Creating a token records an initial supply but mints nothing, so the
instrument reads "Total supply 0" with no explanation and looks broken.
Surface the recorded figure as declared_supply on the instrument summary
and annotate the tile ("0 · declared 2,001") only while it differs from
what is actually minted, alongside a "Mint N remaining" action that
prefills the outstanding amount. Once fully minted the note and the
action disappear, so a settled instrument is just its number.
requireFields could only report an index — a missing recipient surfaced
in the Web UI as "mint: field at position 2 is required". Take
name/value pairs so the error names the input ("mint: recipient party is
required"); this text goes straight into the form.
The party-alias error said an alias "must be a letter followed by
letters, digits or hyphens" while the pattern requires lowercase, so
"Zheholder" looked legal. Say lowercase.
The initial supply recorded at create was never checked, so an instrument
declaring 2001 could be minted to 200,004 — the figure the operator typed
meant nothing. Reject a mint that would push circulating supply past the
declared amount, naming the numbers and the headroom left:
mint exceeds the instrument's declared supply: ZHE declares 2001,
199994 already minted — at most 0 more can be minted
The cap lives in RunMint, so the CLI and the Web UI enforce it
identically; the handler maps it to 422 SUPPLY_CAP_EXCEEDED so the form
can show the numbers. Instruments with no declared supply (Amulet,
anything not created here) stay uncapped, and a scan failure lets the
mint through rather than blocking on an unrelated read error.
This is devkit bookkeeping, not a ledger invariant — the test token's
TokenRules has no cap, so a client minting against it directly still can.
The supply tile therefore also reports an overshoot ('declared 2,001 ·
over by 197,993') instead of hiding it, which is the state instruments
minted before this change are already in.
Comparisons use big.Rat: float drifts past ~15 digits, and supply
figures carry ten decimal places.
The faucet defaulted its source to the role's own party. That holds for Amulet, which the LocalNet bootstrap funds, but a token created here starts with its supply wherever it was minted — so funding from a created instrument always failed with "sender holds no units of this instrument" even though supply existed. Default to the instrument's largest current holder instead, which is what a faucet should dispense from; --source still overrides, and an instrument nobody holds yields the same (now accurate) error. CLI help updated to match. The transfer modal offered an enabled "Atomic" checkbox whose own warning said the submit would fail: ExecuteBatch on this Splice version cannot rebind the accept leg to the instruction the transfer leg creates, so every attempt errors with CONTRACT_DOES_NOT_IMPLEMENT_INTERFACE. A control whose only outcome is an error should not be armable — it stays visible, and labelled unavailable, so the gap against the CLI's --atomic is still documented.
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.
Summary
A batch of token UX bugs surfaced while testing the mint/transfer flow on a live LocalNet. Each was a case where the tool quietly did something other than what the operator was told. All fixes land on both the CLI and the Web UI through the shared orchestration, per the CLI ↔ Web UI parity rule.
What was wrong, and the fix
1. "Total supply 0" on a token created with a supply.
token create --initial-supply 2001only records the figure as registry metadata — it mints nothing — so a created-but-unminted instrument correctly read 0, with nothing explaining why. The supply tile now shows0 · declared 2,001with a Mint initial supply action, and the annotation disappears once minted, so a settled instrument is just its number. Also in this commit:requireFieldsnow names the missing input (mint: recipient party is required) instead offield at position 2, and the party-alias error says the pattern is lowercase (it always was, but the message didn't say so, makingZheholderlook legal).2. Declared supply was never enforced. You could mint 200,004 against a declared 2,001 — the number the operator typed meant nothing. Mint now rejects anything that would push circulating supply past the declared amount, naming the headroom left. Instruments with no declared supply (Amulet, anything not created here) stay uncapped. The supply tile also surfaces an overshoot (
declared 2,001 · over by 197,993) for instruments minted past their declared supply before the cap existed.3. Faucet always failed for created tokens, and the UI offered a control that couldn't work. The faucet defaulted its source to the role's own party — right for Amulet (bootstrap-funded) but wrong for a created token whose supply sits with whoever it was minted to, so it failed with "sender holds no units of this instrument". It now dispenses from the instrument's largest holder (
--sourcestill overrides). Separately, the transfer modal shipped an enabled "Atomic" checkbox whose own warning said the submit would fail (batching can't rebind the accept leg on this Splice version); it's now shown-but-unavailable so it can't be armed.A caveat worth stating
The supply cap is devkit-side bookkeeping, not a ledger invariant — the test token's
TokenRuleshas no on-ledger cap, so a client minting against it directly still can. It makes the number meaningful in this tool and surfaces overshoots honestly; it does not make it enforceable on-chain.Testing
big.Rat(float drifts past ~15 digits and supply carries 10 decimals).go test ./internal/...,golangci-lint,tsc, and the frontend test suite pass.dist/index.htmlkept as the placeholder.Parity
All three fixes go through the shared
RunCreate/RunMint/RunFaucetand sharedinternal/api/types, so the CLI--jsonand the Web UI emit the same shapes and enforce the same guards.