Skip to content

Fee-multiplier ceiling, token symbol consistency, sliding session TTL, scoped query invalidation (#428–#431) - #438

Merged
Jaydbrown merged 10 commits into
conduit-protocol:mainfrom
bade2brazy:fix/fee-multiplier-token-symbols-session-ttl-query-scope
Aug 31, 2026
Merged

Fee-multiplier ceiling, token symbol consistency, sliding session TTL, scoped query invalidation (#428–#431)#438
Jaydbrown merged 10 commits into
conduit-protocol:mainfrom
bade2brazy:fix/fee-multiplier-token-symbols-session-ttl-query-scope

Conversation

@bade2brazy

Copy link
Copy Markdown
Contributor

Four Stellar Wave enhancements, one commit pair per issue.

#428getFeeMultiplier() had a floor but no ceiling

lib/env.ts: a mistyped NEXT_PUBLIC_SOROBAN_FEE_MULTIPLIER=200 (meant as 2.00) bid 200× the recent inclusion fee on every transaction, capped only by MAX_INCLUSION_FEE (0.1 XLM). getFeeMultiplier() now clamps to [1, 10]: a non-numeric, non-positive, or out-of-range value is rejected with a console.warn and the default is used — the same defensive posture the rest of env.ts takes. Tests cover the default, the in-range values and boundaries, and every rejected case.

#429 — testnet/mainnet token symbol sets are inconsistent

TOKENS_TESTNET has EURC; TOKENS_MAINNET does not, so tokenBySymbol('EURC', 'mainnet') returns undefined after a network switch and /create / top-up flows that assume a resolved TokenMeta break.

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's XLM instead of undefined. networksForSymbol / networksForAddress report 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 EURC to TOKENS_MAINNET with Circle's verified mainnet issuer and its Asset('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: expiresAt was 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 to ttlMs from now; it is a no-op for an absent or already-expired session (an expired one is still cleared, as before).
  • WalletProvider calls it when it restores a valid session on mount, and after every successful signTx. An idle session still lapses; an active one slides.

#431StreamActions.run() invalidates every query in the app

components/stream/StreamActions.tsx: after any stream action, queryClient.invalidateQueries() with no filter refetched wallet balance, allowance, the full /streams list, /dashboard, /transactions, etc.


Not built or run in this environment. All changes are typed against the repo's strict + noUncheckedIndexedAccess tsconfig, and the new tests follow the existing Vitest patterns (vi.spyOn, the Map-backed localStorage stub, dynamic import('./env.js')). CI (npm run lint / typecheck / test / build) has not run.

Closes #428, closes #429, closes #430, closes #431

`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
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@Jaydbrown
Jaydbrown merged commit a449201 into conduit-protocol:main Aug 31, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment