Repository navigation
feat(ui): add custom token import with unverified asset disclaimer (#789) - #849
Merged
Jaydbrown merged 1 commit intoOct 22, 2026
Merged
Conversation
…onduit-protocol#789) Let users paste a Soroban contract address into the TokenSelector search and import tokens that are not in the curated list, so newly deployed tokens can be streamed on day one. - lib/tokens: add `verified` flag to TokenMeta and `fetchSep41Metadata`, which reads name/symbol/decimals via `simulateReadOnly`, validates the address with StrKey and rejects malformed metadata - lib/imported-tokens-storage: per-network local persistence for imported tokens, always flagged unverified, tolerant of tampered storage - TokenSelector: "Import token" action for unknown C-addresses, loading and error/retry states, an "Unverified Asset" modal that requires an explicit acknowledgement checkbox, and an "Unverified" badge in the list - new optional `sourceAddress` prop supplies the simulation source Closes conduit-protocol#789 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jaydbrown
approved these changes
Oct 22, 2026
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
Users can now paste an arbitrary Soroban contract address into the
TokenSelectorsearch and import a newly deployed token, so protocols can get streaming support on day one. Before anything is added, they see an Unverified Asset disclaimer that they must explicitly acknowledge. Imported tokens are saved locally and labeled Unverified in the list.Closes #789
Changes
lib/tokens.tsTokenMetagets an optionalverified?: boolean. Curated entries leave it unset and imported tokens arefalse.TokenSelectoralready readt.verified, which was a typecheck error onmain(TS2339), so that error is fixed too.fetchSep41Metadata(contractId, source, { signal, timeoutMs })reads SEP-41name,symbolanddecimalsthrough the existingsimulateReadOnlyRPC client, so it gets the same timeouts, abort handling and circuit breaker as other calls. It validates the address withStrKey, needs a valid funded source account, rejects empty or oversized strings and decimals outside 0–38, and wraps simulation failures with a "may not be a SEP-41 token" hint.lib/imported-tokens-storage.ts(new): imported tokens are stored per network (mainnet,testnet,local) underconduit:imported-tokens. It uses the same safelocalStorage/ in-memory fallback pattern asonboarding-storage.tsandstream-notes-storage.ts. Entries are always forced toverified: false, re-importing an address replaces the old entry, and malformed or tampered entries are dropped when loading.components/TokenSelector.tsxStrKey, including the checksum) that isn't in the curated or imported list, the selector offers Import token.Reading token metadata…, announced viarole="status"). A failure shows an inlinerole="alert"error and the user can retry. Changing the query or unmounting cancels the in-flight lookup.components/ui/Modal, which provides the focus trap, Escape to close, closing on overlay click and returning focus afterwards. It shows the name, symbol, decimals and full contract address. Import token stays disabled until the labeled checkbox "I understand this token is unverified…" is ticked; the checkbox is linked to the warning text witharia-describedby.onChangeand clears the search. Imported tokens show up in search results with an Unverified badge, and the resolved state reads "(unverified, imported by you)".sourceAddress(normally the connected wallet's public key) is used as the simulation source. Without it, the import button is disabled and the reason is shown: "Connect a wallet to look up token metadata."How it was tested
New and updated Vitest suites:
components/TokenSelector.import.test.tsx(13 tests) covers:aria-modallib/imported-tokens-storage.test.ts(8 tests) covers saving, per-network scoping, replacement, removal, rejecting invalid addresses, and tampered or unparseable storage.lib/tokens.test.tshas 6 newfetchSep41Metadatatests: success, invalid address, missing source, simulation failure, empty symbol and out-of-range decimals.Local results:
npx vitest run components/TokenSelector lib/tokens.test.ts lib/imported-tokens-storage.test.ts components/ui/Modal.test.tsxgives 5 files, 81 tests, all passing.next lint --file …on every touched file reports no ESLint warnings or errors.tsc --noEmitreports 0 errors in touched files (the one onmaininTokenSelector.tsxis gone). For this check I excludedapp/dashboard/page.tsx, because its parse error stopstscbefore it reports any type errors.npx vitest run: 26 failing files / 85 failing tests on bothmainand this branch, the same counts, and none of the failures are in files this PR touches. The 2 new test files pass.Pre-existing failures on
main(not caused by this PR)CI fails on
mainbefore any of this code runs. For example, run 39464762854 oncf5b2b0fails every job at theRun npm cistep. The four runs onmainbefore it fail the same way.npm cifails becausepackage-lock.jsonis out of sync withpackage.json(Missing: playwright-core@1.64.1 from lock file,Missing: playwright@1.62.1,sentry@0.44.1, …). All CI jobs stop there, so they will be red on this PR too.app/dashboard/page.tsxhas a JSX syntax error around lines 438–445 (TS17002 Expected corresponding JSX closing tag for 'div'). It breaksnpm run typecheck,npm run buildand the 4app/dashboard/__tests__/*suites.npm run lintalready has errors (import/no-duplicatesincomponents/stream/__tests__/StreamFlowPipe.test.tsxandcomponents/ui/__tests__/ui-upgrades.test.tsx).main: 26 files / 85 tests (for exampleapp/create/__tests__/page.test.tsx,app/streams/__tests__/page.test.tsx,components/ThemeToggle.test.tsx,lib/stream-notes-storage.test.ts).I left these out of this PR to keep it focused and avoid conflicts with other open PRs. Each one would be a small separate fix, starting with regenerating the lockfile.
Notes
sourceAddresstoTokenSelectoryet; the component isn't mounted on any page today. Callers can passuseWallet().publicKeywhen they wire it in.🤖 Generated with Claude Code