dc3a87c0e6fbe98edcc6244b923ffaaeb76807ed
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dc3a87c0e6
|
feat(ui): pending reversible transfers with a measured countdown and a cancel
ci / gate (push) Waiting to run
The accounts page lists PendingTransfersBySender for every account of the open wallet, refreshed every five seconds, with when each executes: at a block, counted down at the block interval the chain manager now measures over its last 32 best heads (exposed as ChainStatus.block_interval_ms, with the profile's nominal time as the fallback and labelled as such), or at a timestamp, counted down by the clock. Cancel prepares ReversibleTransfers.cancel and goes through the same review pane as a send, which moves out of the send route into ReviewAndSign. The call decoder now renders a 32-byte argument by its metadata type: an AccountId32 as SS58, anything else as hex. Before, a cancel's tx_id was dressed up as an address. Unit tests cover both the interval and the rendering; the remaining Option<u64> fields are typed `number | null`. On the dev node: a 2 DEV transfer with a 60-block window listed at block 314 with "49 blocks, about 52 s" against a measured 1.1 s interval; a 3 DEV transfer with a 1000-block window cancelled through review, the tx_id shown as hex, and the balance back up by 3 less the fee. Block 314 arrived 14 s after the estimate: the dev chain's interval swung between 0.6 and 1.7 s per ten blocks over that minute. See the issue for what that means for the acceptance criterion. Closes #29 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
52774f1d88
|
feat(ui): send: a form, a review of what Rust decoded, then sign and watch
ci / gate (push) Successful in 9m49s
The form collects text. Review shows the call decoded from the bytes that will be signed, from PreparedTransferInfo: pallet and call, each argument as Rust rendered it, the fee, the mortality and the nonce. Submit hands back only the prepared id, so nothing the form holds can reach the signature; Back discards the prepared transaction on the Rust side. A cancellation window is offered as presets or a custom block count, described in the chain's nominal block time, and hidden for a high-security account whose delay is fixed. After submit the tx:status events drive a stage list until finalized, failed or dropped. Event subscriptions now go through one helper that copes with `listen` resolving after the effect is torn down, which under StrictMode left a second listener alive and showed every stage twice. Optional u64 fields are typed `number | null` rather than `number`. On the dev node, from crystal_alice: 3 DEV reviewed as Balances.transfer_keep_alive with the recipient and "3 DEV", signed and in a block; 2 DEV with a 200-block window reviewed as ReversibleTransfers.schedule_transfer_with_delay, in a block, and then listed by reversible_pending with cancellable_by_me; a prefix-42 address refused at prepare with the cross-chain message; 5,000,000 DEV signed and reported Failed: Arithmetic(Underflow). Closes #28 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
e4cde9a3db
|
feat(ui): receive: address, QR, checkphrase and copy; accounts named in place
ci / gate (push) Waiting to run
The receive screen shows one account's address as text, as a QR (drawn in the webview by qrcode-generator, since an address is public), and as the five-word checkphrase Rust derives, with a copy button through tauri-plugin-clipboard-manager granted only write-text, with its reason. Accounts can be named in place; the labels live in settings under account_names, keyed by address, and show on the receive screen too. Checked against the dev node: the checkphrase for crystal_alice reads Toast-Terrific-Kitten-Warrior-Churn, the same as `quantus wallet list`; a `quantus send` of 7 DEV from crystal_bob raised the accounts row from 99,957.786510548 to 99,964.786510548 within the next head; Copy flips to Copied; a rename is written to settings.json and survives a lock. Closes #27 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
7101ead194
|
feat(ui): onboarding: create a phrase, restore one, or open a quantus-cli file
ci / gate (push) Waiting to run
Three routes under /onboarding, reachable from the lock screen and from the shell, since a second wallet is the same flow as the first. Create picks a scheme (ML-DSA-65 by default), shows the 24 words once, and stands the backup challenge between seeing them and having a wallet; a failed challenge drops the phrase on the Rust side and starts over. Restore takes a phrase and a scheme, with a note that the same phrase gives a different account under each. Import opens a native file chooser through tauri-plugin-dialog, the first plugin, granted only dialog:allow-open with its reason in the capability file; the path goes to Rust, which reads the file. Every path ends unlocked on the accounts page. Driven through the WebKit inspector against the dev node: create wrote onboard_test.json and unlocked; restoring the same phrase under a new name produced the same address; importing a fixture file with the right password opened it and with a wrong one mapped to wrong_password. The chooser itself is a Wayland window nothing here can type into, so the permission is verified by the open promise staying pending rather than rejecting; CLAUDE.md and the inspector script now say so. Closes #26 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
2fcc519215 |
fix(app): a dev CSP so the Vite page renders, one data directory, and a way to verify the webview
ci / gate (push) Successful in 9m16s
Running the shell for the first time showed a blank white window and no error anywhere: Vite's dev page injects an inline React Refresh preamble, the production CSP forbids inline scripts, and every component module then throws on `$RefreshReg$` before React mounts. `tauri.conf.json` now carries a `devCsp` for the dev server's origin; the production CSP is unchanged. `WALLET_DATA_DIR` relocates wallets, settings and profile overrides together, so a dev run lives in a throwaway directory instead of the real app data; `WALLET_KEYSTORE_DIR` still moves only the wallets, for sharing them with quantus-cli in place. Verified end to end against the local dev node by driving the webview through WebKitGTK's remote inspector (`script/webview-inspect.py`, kept with the reason in CLAUDE.md): the lock screen lists both dev accounts; unlocking crystal_alice shows 99,957.786510548 DEV at block 1832, 100 from final, with the status bar on ws://127.0.0.1:9944 at spec 152; the block number advances on its own; a settings change lands in settings.json; the idle rule locks the UI without a click after the configured minute and the Rust log says so; the Lock button locks; the console shows nothing but Vite connecting. GNOME refuses D-Bus screenshots and ImageMagick captures WebKit's GPU surface as flat grey, so the DOM was the evidence. Refs #25 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
fbb7c71db9 |
feat(ui): the app shell: lock screen, sidebar, network switcher, status bar, settings
ci / gate (push) Successful in 9m14s
The frame every screen sits in. A lock screen fronts everything while the session is locked: pick a wallet file, type a password, and the password goes to Rust and nowhere else. Unlocked, a sidebar carries the sections, the network switcher and a lock button with the minutes until the idle rule fires; a status bar shows the chain in use, its endpoint, best and finalized heights and spec version, and says "connecting…" or the last error otherwise. Testnets are labelled in the switcher, the status bar and above their content, as on the observer: a balance on a testnet is not money and the chrome says so before the number does. Settings persist as JSON in the app-data directory, written atomically: network, idle lock, whether testnets are offered. Changing the network re-keys every query; changing the idle lock takes effect on the next check. The theme (system, dark, light) is a local preference applied before first paint from the entry point, because the CSP allows no inline script. Design tokens are the observer's, lifted whole so the wallet and the scoreboard read as one family. The accounts page shows spendable and total per account with the block the numbers were read at and how far that is from final, refreshed by Rust on every head; send, receive and history are placeholders naming their issues. Wide integers now cross the IPC typed as `number`: they are JSON numbers on the wire, which `bigint` cannot be serialised to, and none of them (block heights, seconds, nonces) approaches 2^53. Amounts still travel as strings. Closes #25 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
be2644f31b |
ci: a pinned quantus-node dev chain in the gate, and a script to run one locally
ci / gate (push) Waiting to run
`script/dev-node.sh` fetches the v1.0.1 release binary once into `.cache/`, checks it against the sha256 the release publishes, and runs `quantus-node --dev` on a throwaway base path. The workflow starts it in the background, waits for `system_chain` to answer `Quantus DevNet`, and runs every ignored test whose name says `dev_node`: the send, reverse, cancel and named-failure path, and the scheme-by-era probe that found the mortality bug. Public-node tests stay ignored in CI. Rehearsed locally on a second node on port 9955: both tests pass in 15 s. Closes #24 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
95bcfe9ca4 |
feat(substrate): transfers prepared for review, priced, signed by the session, submitted and watched
ci / gate (push) Waiting to run
`SubstrateAdapter` implements the chain-adapter port for Quantus. `prepare` builds the call from a chain-neutral intent, decodes it back from its own bytes through the runtime metadata into a summary a person can check (a 32-byte account renders as the chain's SS58 address, an amount in the token's units), prices it by assembling a full extrinsic with a zeroed signature of the right size and asking `TransactionPaymentApi_query_info`, and returns the signer payload for the session to sign. `submit` refuses if the runtime spec moved or the payload no longer matches, assembles with the session's signature, and streams statuses; a dispatch error is reported at inclusion, named `Pallet::Variant` where the chain names it, so nobody waits for finality to learn a transfer failed. Reversible transfers: `reversible_for` routes through `ReversibleTransfers::schedule_transfer_with_delay`; a high-security account is routed through the pallet's default-delay call whether it asked or not, since the chain's extension refuses a plain transfer from one. `pending_reversible` lists what is waiting with the execution block from the scheduler's lookup, whose task name is the tx id itself, and `CancelReversible` cancels. Three things the dev node taught, each now written into the code: - Mortality must be anchored at the *best* block. Finality on this proof-of-work chain trails the tip by a hundred blocks, and an era born at the finalized head names a birth block whose hash the runtime resolves differently: "Transaction has a bad signature", for both schemes, while immortal transactions from the same keys passed. Probed all four scheme-by-era combinations to isolate it. - Everything the UI shows is read at the best block, with the block number and the finalized height alongside so confirmations can be shown. subxt's `at_latest()` is the finalized block; the pending list read there showed the state of a hundred blocks ago while the chain's events showed every schedule landing. - subxt's runtime-update subscription died on this node with "header not found" after a reorg and took the connection with it. It is advisory now: on error the runtime version is polled every ten heads and a change forces a reconnect, which refetches metadata. Live, against `quantus-node --dev` with the funded dev accounts: a 3 DEV transfer from crystal_alice to crystal_bob is included and moves bob's balance; a reversible transfer with a 200-block window is scheduled, shows as pending with its execution block, and is cancelled; an overspend fails as `Token(FundsUnavailable)`. Fee for a plain transfer: 0.010157927 DEV. In the app: `transfer_prepare` (amount parsed from its decimal form in Rust), `transfer_submit` (signs through the session, emits `tx:status`), `transfer_discard`, `reversible_pending`, `reversible_cancel_prepare`. Closes #22 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
26559ee148 |
feat(substrate): balances, nonce and high-security status, refreshed on every finalized head
ci / gate (push) Successful in 6m43s
`account_info` reads `System::Account` and `ReversibleTransfers::HighSecurityAccounts` at the latest finalized block through subxt's dynamic storage API, so a number the UI shows cannot be reorganised away and no generated code has to track the runtime. An account the chain has never seen is zero and nonce zero, not an error; the live test reads the dev phrase's account on Planck and gets exactly that. The next nonce comes from `system_accountNextIndex`, which counts the pool. Spendable is free less frozen; total is free plus reserved. A high-security account carries its guardian and its delay, in blocks or milliseconds as the pallet stores it, so the send path (#22) can route it through the reversible-transfers pallet instead of a plain transfer the extension would reject. The connection manager now publishes finalized heads on a watch channel. `balances_watch` spawns one task per chain that re-reads every open account on each head and emits `balances:updated`; `balances` reads once. Both refuse with `Locked` when no wallet is open. Closes #21 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
78c06120f6 |
feat(substrate): a supervised connection per chain, failing over and following upgrades
ci / gate (push) Waiting to run
`ChainManager::start(profile)` spawns a supervisor that tries the profile's endpoints in order, refuses a node whose genesis is not the profile's (by name, so a typo in an override file cannot quietly sign for the wrong chain), records the runtime version at connect, follows finalized heads, applies subxt's runtime updates as the chain upgrades, and on any failure rotates to the next endpoint after an exponential backoff with full jitter, capped at a minute. A head that fails to arrive for two minutes counts as a failure. `status()` reports the endpoint in use, spec and transaction version, finalized height, head age, the last error and the failure count. Backoff is a pure type with the schedule pinned in tests. The live test gives Planck's profile a dead first endpoint and the real second one: it connects to the second at spec 148 with zero failures, and mainnet's profile pointed at Planck's node is refused with "is not quantus". Supervisors start on demand from `chain_connect`, never at boot, because a profile whose endpoint does not exist yet (#53) would otherwise retry forever for nobody. `chain_status` reads without starting anything. Not yet exercised: a stream dropping mid-session and a runtime upgrade arriving while connected. Both go through the same loop and will be driven on the dev node in #24. Closes #20 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
1ca42fdbdf |
feat(substrate): a subxt config for quantus, and offline signing under the context
ci / gate (push) Waiting to run
`QuantusConfig` is quantus-cli's `ChainConfig` with the signature enum
defined here: `DilithiumSignature { Dilithium87, Dilithium65 }`, variant
indices 0 and 1, each carrying signature-then-public-key as one raw array
because the account id is a hash of the key and the verifier needs the key
itself. Defining it locally keeps sp-core out of a desktop wallet.
subxt 0.44, deliberately: it is the version quantus-cli signs mainnet with,
and its extension resolver skips any extension whose type is empty, which
is how the chain's ReversibleTransactionExtension and
WormholeProofRecorderExtension are tolerated with the default set. A test
pins that both names are in the metadata and that building still works.
`sign_call` builds the v4 extrinsic offline from captured chain state,
takes the signer payload from subxt (blake2-hashed past 256 bytes, as the
runtime hashes when it verifies), signs it with a `wallet_core::Signer`
under the profile's FIPS 204 context, and hands the signature back to
subxt to assemble. The test decodes the result byte by byte: 0x84, Id
address equal to the signer's account id, the variant byte, the public key
riding with the signature, the extras, the call data as the tail.
Live acceptance against a1-planck.quantus.cat, from an unfunded account:
signed under QUANTUS_EXTRINSIC the node answers "Inability to pay some
fees", so the signature passed; signed under the empty context it answers
"Transaction has a bad signature". Ignored test, run by hand.
Metadata fixtures are live `state_getMetadata` from mainnet (spec 152) and
Planck (spec 148), 101 KB each, v14.
Closes #19
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW
|
||
|
|
f0b3c26714 |
feat(profiles): chains as data, shipped in the binary and overridable by file
ci / gate (push) Successful in 6m18s
`wallet-core/src/profiles.toml` carries what runtime metadata does not: genesis hash, SS58 prefix, symbol and decimals, coin type, the endpoint list, block time, and the rule for when the FIPS 204 signing context applies. Three profiles ship: Quantus mainnet at spec 152 with QTC, Planck with PLK (in the wallet only to prove the multi-chain UX), and a local dev node with an empty genesis hash that means "do not check". Endpoints are the names decided on #20 and #53, one per chain; until those exist an override file points a dev run at the public nodes. The override file is the same shape with every field optional: a known id has fields replaced, an unknown id must be complete and is appended. A mainnet endpoint must be wss, a genesis hash must be 66 characters or empty, a context at most 255 bytes; anything else is refused with the profile named. Adding a fourth chain is a TOML entry and no Rust, which the test does. `signing_context_for(profile, spec)` and `genesis_matches(profile, hash)` are the two pure functions the adapter (#19, #20) will call at connect. The app loads `<app data>/profiles.toml` or WALLET_PROFILES and exposes `chains_list`. Closes #18 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
18caa2b81c |
feat(session): password in, signatures out, locked again after idling
ci / gate (push) Waiting to run
`wallet_core::session::Session` is the only place decrypted key material lives between commands: one wallet open at a time, its signers and, when the file carried a phrase, its seed. Every signature goes through `Session::sign`, which applies the idle rule before it looks at the key, so a stale session refuses rather than signing one last time. Time is passed in, so the rule is tested at 59, 118 and 119 seconds without waiting. The seed is now a heap allocation behind a wiping box. Moving an inline 64-byte array through `Some(...)` and a function argument can leave copies in dead stack frames that no destructor visits; moving a box moves a pointer. A Linux test unlocks with a runtime-built seed and a signer whose secret is a second runtime-built pattern, locks, then reads every writable anonymous mapping in /proc/self/mem and asserts neither pattern is anywhere in the process. It passes in debug and release. The comparison copies are XOR-masked so the test cannot find itself. In the app: `unlock` opens a file, turns its phrase into a seed and drops the phrase; `lock` and `session_status` do what they say; a ticker thread applies the idle rule every five seconds and emits `session:locked` so the lock screen appears without a click. Ten minutes until settings exist. Closes #17 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
de55d1dfb9 |
feat(mnemonic): generate, validate, verify the backup, and the checkphrase
ci / gate (push) Has been cancelled
A new wallet's phrase is 24 words from OS entropy through the hdwallet crate, which wipes both the entropy and the phrase's own working buffers. Validation runs the same pinned bip39 the hdwallet crate stretches with, so a phrase that validates here derives there; it normalises case and whitespace and rejects the wrong count, an unknown word, and a bad checksum with an error a person can act on. The checkphrase is Quantus's human-readable address checksum through qp-human-checkphrase, with the 2048-word list vendored because the crate loads it from the working directory at runtime, which a desktop app cannot rely on. The value for the dev phrase's ML-DSA-65 account is pinned to what `quantus wallet view` prints. Creation is three commands so the webview sees a phrase exactly once: begin generates and holds it and returns the words with a four-word backup challenge; confirm passes the challenge or refuses, then derives at the scheme's conventional path and writes the file; cancel forgets. No command returns a phrase that already exists. Import from a phrase and from a CLI file, and listing, are the other commands. App state is a mutex around the pending creation; the keystore directory is the platform app-data directory unless WALLET_KEYSTORE_DIR says otherwise. Closes #16 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
5776cfd218 |
feat(keystore): read and write quantus-cli wallet files, byte for byte
ci / gate (push) Successful in 4m56s
`wallet_data::keystore` is the CLI's format: AES-256-GCM over the JSON of the wallet data, keyed by Argon2id under the frozen profile (m=19456, t=2, p=1) and a 16-byte salt, with the PHC string carrying the profile and salt and never the digest. A file whose PHC string does embed a digest is refused, as the CLI refuses it. On open the address is re-derived from the decrypted key and the file refused if it differs, so a file that lies about its address cannot be opened, let alone signed with. Legacy files with no `scheme` are ML-DSA-87; files with no `wallet_type` are hot. The directory rules are the CLI's too: a wallet name is a plain filename, writes go through an unpredictable temporary file and a no-clobber rename at mode 0600, and a symlink where a wallet should be is refused. Proof in both directions. Eight real files open here: six written by quantus-cli 2.2.2 for the dev phrase, one per scheme and path, and the two fixtures the extension fork tests against. A file written here lists in `quantus wallet list` with the expected address (ignored test, run with QUANTUS_CLI set). A test greps the written file for phrase words and secret key bytes and finds neither. `Keystore` is a port in wallet-core: list, open, create, import a file, export a file. Opening yields a `Signer` and, when the file carries one, the phrase in a wiping holder; neither leaves Rust. Closes #15 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
8015ca8212 |
feat(keys): ML-DSA key schemes, derived at a BIP44 path, signed under a context
ci / gate (push) Successful in 4m38s
`wallet_data::keys::MlDsa` implements the `KeyScheme` port for ML-DSA-65 and ML-DSA-87 on Quantus's own crates: hdwallet for the all-hardened `m/44'/189189'/account'/change'/index'` derivation, dilithium for FIPS 204 signing, poseidon for the account id the runtime derives from a public key. Signing is hedged with fresh OS randomness, and an empty context means "no context", the pre-spec-148 rule, rather than a zero-length one. The test vectors are six addresses produced by quantus-cli 2.2.2 for the polkadot-js dev phrase, three paths per scheme; two of them are also the addresses inside the extension fork's CLI-wallet fixtures. A signature made under `QUANTUS_EXTRINSIC` verifies, and the same signature under the empty context does not, which is the mistake that would otherwise reach mainnet as a cryptographically valid, chain-rejected transaction. `Signer` now requires `Debug`, so every implementation has to write one, and the one here redacts. The seed is a 64-byte wiping holder with a `Debug` that prints nothing. SS58 lives in `wallet_core::ss58`, blake2 and base58 only; decoding returns the prefix so a foreign address can be refused by name. Closes #14 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
d951ce07e7 |
feat(ui): a typed command client, and one file allowed to touch the IPC
ci / gate (push) Waiting to run
Commands now return `Result<T, WalletError>`; Tauri rejects the promise with the serialised error, and `ui/src/api/wallet.ts` is the one place that turns it back into a thrown `WalletError` with the same `kind` the Rust enum has. Anything that is not a `WalletError` is a bug on one side of the boundary and is rethrown untouched so it is loud. Screens never call `invoke` themselves. React Query holds what Rust knows, with retries off (a wallet must not silently retry a command that failed) and refetch-on-focus off. CI counts the files importing Tauri's IPC module and fails unless the count is exactly one. The first command is `app_info`, so the window shows a version that came across the boundary as a typed struct rather than a string. Closes #13 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
c9b623c438 |
feat(app): isolation pattern on, and a capability file that must say why
The IPC isolation pattern is on from the first command: every message from the main window passes through the sandboxed isolation application and is encrypted before Rust sees it, so a script injected into the webview can read what the UI shows but cannot forge a command. The hook passes everything through today; the point of shipping it now is that every later command is born inside the boundary. Both `tauri` and `tauri-build` need the `isolation` feature, and the build script refuses to proceed if either lacks it. Capabilities are TOML rather than JSON so each permission can carry the reason it exists on the line above it. `tests/capabilities.rs` makes that comment mandatory, requires a real description on every capability, and refuses to grant anything to a window other than `main`. A capability is the one place a plugin's reach into the system is widened, and it should not be possible to widen it quietly. Closes #11 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |
||
|
|
257b021066 |
feat: scaffold the workspace, the tauri shell and the webview
ci / gate (push) Successful in 4m24s
The house shape (architecture/generic.md §1): wallet-entities for the vocabulary and the IPC wire types, wallet-core for the four ports that are the architecture (KeyScheme, ChainAdapter, SwapProvider, FiatRamp), wallet-data for the adapters that will implement them, and wallet-app as a Tauri 2 shell whose commands are one-line shims. The webview is Vite + React + TypeScript and reaches Rust only through ui/src/api/wallet.ts, typed by what ts-rs exports from the entities crate on every `cargo test`. Amounts are integer base units plus decimals, serialised as strings, because the webview has no integer wide enough and a wallet that rounds is a wallet that lies. The webview never receives a secret; readme.md and CLAUDE.md say why and what will bite. Two things the scaffold learned: Tauri's `version` field takes a JSON file or a literal, never Cargo.toml, so the release workflow stamps it; and Prettier must skip the generated types, which carry ts-rs's own style. Placeholder icon; the mark comes later. CI targets the rust-gtk3 runner. Refs #1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ftBXYuba8ARhQeF74oUgW |