Fee-multiplier ceiling, token symbol consistency, sliding session TTL, scoped query invalidation (#428–#431) - #438
Merged
Jaydbrown merged 10 commits intoAug 31, 2026
Conversation
`getFeeMultiplier()` had a floor but no ceiling, so a mistyped `NEXT_PUBLIC_SOROBAN_FEE_MULTIPLIER=200` (for `2.00`) bid 200x the recent inclusion fee on every transaction, backstopped only by MAX_INCLUSION_FEE (0.1 XLM). It now rejects a non-numeric, non-positive, or out-of-[1, 10] value with a console.warn and uses the default, matching env.ts's posture. Closes conduit-protocol#428
Default, in-range values and boundaries, and the rejected cases (200, non-numeric, zero, negative) with the warn. Refs conduit-protocol#428
`TOKENS_TESTNET` has EURC and `TOKENS_MAINNET` does not (conduit-protocol#429), so a symbol carried over a network switch resolves to `undefined` and breaks callers that assume a `TokenMeta`. `resolveTokenBySymbol` falls back to the network's XLM with `wasReset: true`; `networksForSymbol` / `networksForAddress` report where a token actually exists. Refs conduit-protocol#429
…work TokenSelector now distinguishes "unknown contract" from "known token, wrong network" and names the network(s) it does exist on (conduit-protocol#429).
The wallet session's `expiresAt` is stamped once at connect, so an active user is force-disconnected exactly 24h later regardless of activity (conduit-protocol#430). `touchWalletSession(ttlMs?)` re-stamps a still-valid session; it is a no-op for an absent or expired session. Refs conduit-protocol#430
WalletProvider calls `touchWalletSession()` when it restores a valid session on mount and after every successful `signTx`, so only a genuinely idle session lapses. Closes conduit-protocol#430
… invalidator `lib/query-keys.ts` gives streams stable keys (`['stream', address, 'info']`, ...) and `invalidateStreamMutation(qc, address)` invalidates only the stream, streams-list, dashboard, transactions, and wallet-balance trees — the foundation for replacing the app-wide `invalidateQueries()` (conduit-protocol#431). Refs conduit-protocol#431
`StreamActions.run()` and `WithdrawButton` called `queryClient.invalidateQueries()` with no filter after any action, refetching wallet balance, allowance, the full /streams list, /dashboard, and /transactions on every pause/resume/ cancel/top-up/clawback/withdraw. Both now use `invalidateStreamMutation`, and `refreshStreamData()` is narrowed to the stream/dashboard/transactions trees (flagged in conduit-protocol#215 / conduit-protocol#354). Closes conduit-protocol#431
|
@bade2brazy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Four
Stellar Waveenhancements, one commit pair per issue.#428 —
getFeeMultiplier()had a floor but no ceilinglib/env.ts: a mistypedNEXT_PUBLIC_SOROBAN_FEE_MULTIPLIER=200(meant as2.00) bid 200× the recent inclusion fee on every transaction, capped only byMAX_INCLUSION_FEE(0.1 XLM).getFeeMultiplier()now clamps to[1, 10]: a non-numeric, non-positive, or out-of-range value is rejected with aconsole.warnand the default2×is used — the same defensive posture the rest ofenv.tstakes. Tests cover the default, the in-range values and boundaries, and every rejected case.#429 — testnet/mainnet token symbol sets are inconsistent
TOKENS_TESTNEThasEURC;TOKENS_MAINNETdoes not, sotokenBySymbol('EURC', 'mainnet')returnsundefinedafter a network switch and/create/ top-up flows that assume a resolvedTokenMetabreak.I took the "detect and reset" path (the issue lists it as an acceptable alternative to adding a mainnet contract). Adding Circle's real mainnet EURC contract is the other option and is the better long-term fix, but I could not verify the exact contract ID from here — see the note below.
lib/tokens.ts:resolveTokenBySymbol(symbol, network)returns{ token, wasReset }, falling back to the network'sXLMinstead ofundefined.networksForSymbol/networksForAddressreport where a token actually exists.components/TokenSelector.tsx: an address that is a known token on another network now shows "This contract is a known token on testnet, but not on mainnet. Switch networks or choose a different token." instead of a generic "unknown".For maintainers: to close the symbol gap properly, add
EURCtoTOKENS_MAINNETwith Circle's verified mainnet issuer and itsAsset('EURC', issuer).contractId(Networks.PUBLIC)value, the same way the testnet entry is derived. The helpers above stay useful as the safety net.#430 — wallet session TTL is a hard 24h from connect
lib/wallet-storage.ts+contexts/WalletContext.tsx:expiresAtwas stamped once at connect, so an active user with the app open was force-disconnected exactly 24h later, mid-session.touchWalletSession(ttlMs?)re-stamps a still-valid session's expiry tottlMsfrom now; it is a no-op for an absent or already-expired session (an expired one is still cleared, as before).WalletProvidercalls it when it restores a valid session on mount, and after every successfulsignTx. An idle session still lapses; an active one slides.#431 —
StreamActions.run()invalidates every query in the appcomponents/stream/StreamActions.tsx: after any stream action,queryClient.invalidateQueries()with no filter refetched wallet balance, allowance, the full/streamslist,/dashboard,/transactions, etc.lib/query-keys.ts: structured keys (queryKeys.streams.detail(address)=['stream', address], a prefix ofinfo/withdrawable) andinvalidateStreamMutation(qc, address)that invalidates only the stream, streams-list, dashboard, transactions, and wallet-balance trees.StreamActions.run()andWithdrawButton(same bug) now call it.refreshStreamData()inlib/queryClient.tsis narrowed to those trees too (flagged in Bug: refreshStreamData double-refetches all active queries on every successful transaction #215 /refreshStreamDataswallows invalidation failures — stale balances persist silently #354).Not built or run in this environment. All changes are typed against the repo's
strict+noUncheckedIndexedAccesstsconfig, and the new tests follow the existing Vitest patterns (vi.spyOn, the Map-backedlocalStoragestub, dynamicimport('./env.js')). CI (npm run lint/typecheck/test/build) has not run.Closes #428, closes #429, closes #430, closes #431