-
Notifications
You must be signed in to change notification settings - Fork 585
SDK: SwapWidget [WIP] #8044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
SDK: SwapWidget [WIP] #8044
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughAdds a new Bridge Swap widget with multi-step UI (token selection, quote loading, preview, execution), new hooks for chains/tokens, supporting UI components, and Storybook stories. Updates design-system primitives (radius, inputs, buttons, text, line, dynamic height), image handling with fallback, spinner/skeleton styling, minor bridge UI text tweaks, a Chain type alias, fiat symbol refactor, and a new npm typecheck script. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant SW as SwapWidget
participant UI as SwapUI
participant Q as QuoteLoader
participant PV as PaymentDetails
participant EX as StepRunner
participant W as Wallet
participant B as Bridge API
U->>SW: Open widget
SW->>UI: Render base swap UI
UI->>W: Check active wallet (optional)
U->>UI: Select sell/buy tokens, enter amount
UI->>B: Fetch quote (poll ~20s)
B-->>UI: Quote result
U->>UI: Click Swap
UI->>SW: onSwap(quote, request)
SW->>Q: Load quote details
Q-->>SW: preparedQuote, request
SW->>PV: Show preview
U->>PV: Confirm
PV->>SW: onConfirm(preparedQuote, request)
SW->>EX: Execute steps
loop For each step
EX->>W: Request transaction/signature
W-->>EX: Tx result
EX->>B: Report / query status
B-->>EX: Status update
end
EX-->>SW: Complete / Cancel / Error
SW-->>U: Final status
sequenceDiagram
autonumber
actor U as User
participant SB as SelectBuyToken
participant CH as useBridgeChains
participant TK as useTokens
participant API as Bridge API
U->>SB: Open "Select Buy Token"
SB->>CH: Query bridge chains
CH-->>SB: Chains list
U->>SB: Choose chain / Search tokens
SB->>TK: Fetch tokens (chainId, search, offset/limit)
TK->>API: tokens({ includePrices:false, name/tokenAddress })
API-->>TK: Token list
TK-->>SB: Tokens
U->>SB: Click token
SB->>API: getToken(...) (with prices)
API-->>SB: TokenWithPrices
SB-->>U: Selection applied
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes ✨ Finishing touches🧪 Generate unit tests
Comment Pre-merge checks❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
size-limit report 📦
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #8044 +/- ##
========================================
Coverage 56.63% 56.64%
========================================
Files 904 904
Lines 58685 58805 +120
Branches 4166 4172 +6
========================================
+ Hits 33238 33310 +72
- Misses 25341 25389 +48
Partials 106 106
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/thirdweb/src/bridge/types/Chain.ts (1)
42-43
: Add required TSDoc for public alias per package guidelinesPublic symbols here must include TSDoc with an example and a custom stability tag.
Add docs:
+ +/** + * Alias of {@link Chain} used across the Bridge UI/API surface. + * @beta + * @public + * @example + * import type { BridgeChain } from "thirdweb/bridge"; + * function onSelect(chain: BridgeChain) { + * console.log(chain.chainId, chain.name); + * } + */ export type BridgeChain = Chain;packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
36-106
: Add TSDoc or mark internal for exported UI primitivesPer repo guidelines, exported symbols in packages/thirdweb need TSDoc with an example or an explicit @internal. Please annotate Label, Input, and InputContainer.
-export const Input = /* @__PURE__ */ StyledInput<InputProps>((props) => { +/** + * @internal + */ +export const Input = /* @__PURE__ */ StyledInput<InputProps>((props) => {packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
26-157
: Add TSDoc or mark internal for Button exportsPlease annotate Button, ButtonLink, and IconButton per packages/thirdweb guidelines.
-export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { +/** + * @internal + */ +export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
37-41
: Fix “undefinedpx” sizing when width/height are omittedwidthPx/heightPx produce invalid values if props are undefined. Provide safe defaults.
- const widthPx = `${props.width}px`; - const heightPx = `${props.height || props.width}px`; + const DEFAULT_SIZE = "24px"; + const widthPx = props.width ? `${props.width}px` : undefined; + const heightPx = props.height + ? `${props.height}px` + : props.width + ? `${props.width}px` + : DEFAULT_SIZE;
🧹 Nitpick comments (51)
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (3)
3-16
: Forward SVG props and add a sane default size.Enable consumers to pass className/aria-label/etc. and avoid undefined width/height.
-export const ArrowUpDownIcon: IconFC = (props) => { +export const ArrowUpDownIcon: IconFC = ({ size = 24, ...props }) => { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" - strokeWidth="2" + strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" - role="presentation" - width={props.size} - height={props.size} + role="presentation" + width={size} + height={size} + {...props} >
13-16
: A11y: mark decorative SVGs as hidden to AT (or provide an accessible name).If this icon is purely decorative, prefer aria-hidden and prevent focus; if it conveys meaning, pass an aria-label via props and keep role="img".
- role="presentation" + aria-hidden="true" + focusable="false"
3-3
: Add TSDoc with a custom tag per packages/thirdweb policy.Mark as @internal if not part of the public API; otherwise add an @example.
+/** + * Up/down arrows icon used in wallet/bridge UIs. + * @internal + */ export const ArrowUpDownIcon: IconFC = ({ size = 24, ...props }) => {packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx (3)
97-98
: Add missing period for consistency and polish.The preceding testnet message ends with a period. Mirror that here.
- This token or chain is not supported by the Bridge + This token or chain is not supported by the Bridge.
30-40
: Don’t use React Query for a side-effect-only analytics ping.Using
useQuery
here adds caching semantics and a static key that won’t re-fire if props change within the same mount. PreferuseEffect
(or auseMutation
) tied to the relevant deps.-import { useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; @@ - useQuery({ - queryFn: () => { - trackPayEvent({ - chainId: chain.id, - client, - event: "ub:ui:unsupported_token", - fromToken: tokenAddress, - }); - }, - queryKey: ["unsupported_token"], - }); + useEffect(() => { + trackPayEvent({ + chainId: chain.id, + client, + event: "ub:ui:unsupported_token", + fromToken: tokenAddress, + }); + }, [chain.id, client, tokenAddress]);If you must keep React Query, at least include
chain.id
andtokenAddress
in thequeryKey
and setstaleTime: Infinity
.
25-25
: Add explicit return type to match TS guidelines.Declare the component’s return type for clarity and tooling.
-export function UnsupportedTokenScreen(props: UnsupportedTokenScreenProps) { +export function UnsupportedTokenScreen(props: UnsupportedTokenScreenProps): JSX.Element {packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx (1)
20-21
: Smoother height animation and consistency with DS transitionsConsider ease-in-out and explicitly hint painting for slightly smoother expansion; also centralize duration/timing as a design token to keep consistent across components.
Apply this minimal tweak:
- transition: "height 210ms ease", + transition: "height 210ms ease-in-out", + willChange: "height",packages/thirdweb/package.json (1)
344-345
: Avoid duplicating tsc invocation between lint and typecheckYou can route lint to call the new typecheck script for one source of truth.
Update scripts:
- "lint": "knip && biome check ./src && tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit", + "lint": "knip && biome check ./src && pnpm typecheck", "typedoc": "node scripts/typedoc.mjs && node scripts/parse.mjs", "typecheck": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit",packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
18-26
: Hide decorative skeletons from assistive techMarking as decorative avoids noisy announcements while loading.
- <SkeletonDiv + <SkeletonDiv + aria-hidden className={props.className || ""} color={props.color} style={{ height: props.height, width: props.width || "auto", ...props.style, }} />packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
1-3
: Make chain-name cleanup more robustLimit removal to trailing “Mainnet”, handle case/parentheses, and avoid returning empty strings.
-export function cleanedChainName(name: string) { - return name.replace("Mainnet", ""); -} +export function cleanedChainName(name: string) { + const cleaned = name.replace(/\s*\(?Mainnet\)?$/i, "").trim(); + return cleaned.length > 0 ? cleaned : name; +}Happy to add a tiny test matrix (e.g., "Ethereum Mainnet", "Mainnet", "Zora (Mainnet)", "Arbitrum mainnet").
packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
120-127
: Hover styling fine; improve transition and default backgroundUse transparent (clearer intent) and animate background for smoother UX.
- transition: "border 200ms ease", + transition: "border 200ms ease, background 200ms ease, color 200ms ease", ... - if (props.variant === "ghost-solid") { + if (props.variant === "ghost-solid") { return { "&:hover": { background: theme.colors.tertiaryBg, }, - border: "1px solid transparent", + background: "transparent", + border: "1px solid transparent", }; }packages/thirdweb/src/pay/convert/type.ts (1)
36-60
: Optional: disambiguate $-prefixed localesConsider locale-specific prefixes (e.g., HK$, CA$, A$) if UI needs clarity across multiple “$” currencies.
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)
1-37
: Mark hooks as internalThese widget-specific hooks should be annotated to avoid being treated as public API.
-export function useTokens(options: { +/** + * @internal + */ +export function useTokens(options: {packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx (1)
164-177
: Simplify background color helperAll branches return the same color; collapse the switch.
- const getStepBackgroundColor = ( - status: "pending" | "executing" | "completed" | "failed", - ) => { - switch (status) { - case "completed": - return theme.colors.tertiaryBg; - case "executing": - return theme.colors.tertiaryBg; - case "failed": - return theme.colors.tertiaryBg; - default: - return theme.colors.tertiaryBg; - } - }; + const getStepBackgroundColor = () => theme.colors.tertiaryBg;packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (2)
5-12
: Include client in queryKey; set a sane cache policyPrevents cross-client cache bleed and reduces refetch churn.
-export function useBridgeChains(client: ThirdwebClient) { - return useQuery({ - queryKey: ["bridge-chains"], - queryFn: () => { - return chains({ client }); - }, - }); -} +export function useBridgeChains(client: ThirdwebClient) { + return useQuery({ + queryKey: ["bridge-chains", client.clientId], + staleTime: 5 * 60 * 1000, + queryFn: () => chains({ client }), + }); +}
5-12
: Mark internalThis is widget plumbing, not a public SDK surface.
-export function useBridgeChains(client: ThirdwebClient) { +/** + * @internal + */ +export function useBridgeChains(client: ThirdwebClient) {packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (3)
17-28
: Make icon decorative and click‑throughSet the search icon to be ignored by screen readers and not intercept pointer events.
<Container color="secondaryText"> <MagnifyingGlassIcon width={iconSize.md} height={iconSize.md} + aria-hidden="true" style={{ position: "absolute", left: spacing.sm, top: "50%", transform: "translateY(-50%)", + pointerEvents: "none", }} /> </Container>
30-38
: Avoid magic number; improve a11yCompute left padding from tokens, set proper input type, and add an accessible label.
<Input - variant="outline" + variant="outline" + type="search" placeholder={props.placeholder} value={props.value} style={{ - paddingLeft: "44px", + paddingLeft: `calc(${spacing.sm} + ${iconSize.md}px + ${spacing.xs})`, }} - onChange={(e) => props.onChange(e.target.value)} + aria-label={props.placeholder} + onChange={(e) => props.onChange(e.target.value)} />
6-10
: Add explicit types per guidelinesAdd a props alias and explicit return type for the component.
-export function SearchInput(props: { - value: string; - onChange: (value: string) => void; - placeholder: string; -}) { +type SearchInputProps = { + value: string; + onChange: (value: string) => void; + placeholder: string; +}; + +export function SearchInput(props: SearchInputProps): JSX.Element {packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx (2)
19-39
: Loading story renders a non-functional “Load More” buttonPassing a no-op showMore causes the button to render while loading. Omit it in the loading story.
-export function ChainLoading() { +export function ChainLoading(): JSX.Element { @@ - showMore={() => {}} setSearch={() => {}} />
42-53
: Add explicit return types to storiesAlign with explicit return types guideline.
-export function WithData() { +export function WithData(): JSX.Element {packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx (3)
13-17
: Add explicit return type-export function SelectChainButton(props: { +export function SelectChainButton(props: { selectedChain: BridgeChain; client: ThirdwebClient; onClick: () => void; -}) { +}): JSX.Element {
19-31
: Button semantics and accessibilityEnsure it doesn’t submit forms and is labeled for screen readers.
<Button variant="secondary" fullWidth + type="button" + aria-label="Select chain" style={{ justifyContent: "flex-start", fontWeight: 500, fontSize: fontSize.md, padding: `${spacing.sm} ${spacing.sm}`, minHeight: "48px", }}
32-39
: Alt text and name cleanupProvide alt text and trim the cleaned name to avoid trailing spaces.
<Img src={props.selectedChain.icon} client={props.client} width={iconSize.lg} height={iconSize.lg} + alt={cleanedChainName(props.selectedChain.name).trim()} /> - <span> {cleanedChainName(props.selectedChain.name)} </span> + <span> {cleanedChainName(props.selectedChain.name).trim()} </span>packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx (2)
19-33
: Add explicit return type-export function WithData() { +export function WithData(): JSX.Element {
35-51
: Add explicit return type-export function Loading() { +export function Loading(): JSX.Element {packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (3)
13-15
: Add explicit return type-export function BasicUsage() { +export function BasicUsage(): JSX.Element {
17-19
: Add explicit return type-export function CurrencySet() { +export function CurrencySet(): JSX.Element {
21-23
: Add explicit return type-export function LightMode() { +export function LightMode(): JSX.Element {packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
60-66
: Remove unused/ineffective flex propertyjustifyItems has no effect on flex containers.
style={{ alignItems: "center", display: "inline-flex", flexShrink: 0, - justifyItems: "center", position: "relative", }}
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (3)
45-51
: Grammar/clarity in Account Abstraction section.Small wording fixes for correctness and tone.
- * Enable Account abstraction for all wallets. This will connect to the users's smart account based on the connected personal wallet and the given options. - * - * This allows to sponsor gas fees for your user's transaction using the thirdweb account abstraction infrastructure. + * Enable Account Abstraction for all wallets. This connects to the user's smart account based on the connected personal wallet and the given options. + * + * This allows you to sponsor gas fees for your user's transactions using thirdweb's Account Abstraction infrastructure.
121-126
: Minor grammar: article before “All Wallets”.- * By default, ConnectButton modal shows a "All Wallets" button that shows a list of 500+ wallets. + * By default, the ConnectButton modal shows an "All Wallets" button that lists 500+ wallets.
129-134
: Typo: “Ethererum” → “Ethereum”.- * Enable SIWE (Sign in with Ethererum) by passing an object of type `SiweAuthOptions` to + * Enable SIWE (Sign in with Ethereum) by passing an object of type `SiweAuthOptions` topackages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx (1)
25-70
: Add explicit return types for story exports.Keep TSX explicit per repo guidelines.
-export function ChainLoading() { +export function ChainLoading(): JSX.Element { ... }-export function Disconnected() { +export function Disconnected(): JSX.Element { ... }packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx (3)
19-26
: Rename prop type to match component purpose and add return types.Type name says “BuyToken” but this is a chain selector. Also add explicit return types.
-type SelectBuyTokenProps = { +type SelectBridgeChainProps = { onBack: () => void; client: ThirdwebClient; onSelectChain: (chain: BridgeChain) => void; selectedChain: BridgeChain | undefined; }; -export function SelectBridgeChain(props: SelectBuyTokenProps) { +export function SelectBridgeChain(props: SelectBridgeChainProps): JSX.Element { ... } export function SelectBridgeChainUI( - props: SelectBuyTokenProps & { + props: SelectBridgeChainProps & { isPending: boolean; chains: BridgeChain[]; onSelectChain: (chain: BridgeChain) => void; selectedChain: BridgeChain | undefined; - }, -) { + }, +): JSX.Element {Also applies to: 39-46
95-99
: Provide keys instead of suppressing lints on skeleton list.-{props.isPending && - new Array(20).fill(0).map(() => ( - // biome-ignore lint/correctness/useJsxKeyInIterable: ok - <ChainButtonSkeleton /> - ))} +{props.isPending && + new Array(20).fill(0).map((_, i) => <ChainButtonSkeleton key={`sk-${i}`} />)}
156-163
: Add accessible alt text for chain icons.- <Img + <Img src={props.chain.icon} client={props.client} width={iconSize.lg} height={iconSize.lg} + alt={`${cleanedChainName(props.chain.name)} icon`} />packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (2)
65-70
: Make className optional in SwapWidgetContainer props.It’s currently required but can be undefined.
-export function SwapWidgetContainer(props: { - theme: SwapWidgetProps["theme"]; - className: string | undefined; +export function SwapWidgetContainer(props: { + theme: SwapWidgetProps["theme"]; + className?: string;
28-30
: Type annotate useActiveWalletInfo() with the exported type.Improves readability and avoids structural drift.
-import type { SwapWidgetConnectOptions } from "./types.js"; +import type { ActiveWalletInfo, SwapWidgetConnectOptions } from "./types.js";-function useActiveWalletInfo() { +function useActiveWalletInfo(): ActiveWalletInfo | undefined {Also applies to: 266-280
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (3)
500-541
: Add an accessible label to the switch button control.- <SwitchButtonInner + <SwitchButtonInner + aria-label="Swap selected tokens" variant="outline" onClick={(e) => {
435-467
: Alt text for token/chain images.- <Img + <Img src={props.selectedToken.iconUri || ""} client={props.client} width={iconSize.lg} height={iconSize.lg} + alt={`${props.selectedToken.symbol} token icon`} style={{ borderRadius: radius.full, }} /> ... - <Img + <Img src={props.chain.icon} client={props.client} width={iconSize.sm} height={iconSize.sm} + alt={`${cleanedChainName(props.chain.name)} icon`} style={{ borderRadius: radius.full, }} />Also applies to: 459-467
50-58
: Explicit return types for exported components.Keep function return types explicit for TSX per guidelines.
-export function SwapUI(props: SwapUIProps) { +export function SwapUI(props: SwapUIProps): JSX.Element {-export function SwapUIBase( +export function SwapUIBase( props: SwapUIProps & { onSelectToken: (type: "buy" | "sell") => void }, -) +) : JSX.Element-function TokenSection(props: { +function TokenSection(props: { ... -}) { +}): JSX.Element {Also applies to: 98-101, 321-331
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx (4)
34-39
: Graceful default chain fallback when preferred chain not presentDefaulting to chainId 1 may return undefined if it’s not in the list. Fallback to first available.
- return chains.find((chain) => chain.chainId === (activeChainId || 1)); + return ( + chains.find((chain) => chain.chainId === (activeChainId || 1)) ?? + chains[0] + );
45-46
: Right-size pagination: start smaller and increment predictablylimit=1000 is heavy for initial render. Start smaller and grow linearly to reduce query/render cost.
- const [limit, setLimit] = useState(1000); + const [limit, setLimit] = useState(50); @@ - tokensQuery.data?.length === limit + tokensQuery.data?.length === limit ? () => { - setLimit(limit * 2); + setLimit(limit + 50); } : undefinedAlso applies to: 95-99
175-183
: Address comparison should be case-insensitiveNormalize both addresses to avoid false negatives.
- isSelected={props.selectedToken?.address === token.address} + isSelected={ + props.selectedToken + ? props.selectedToken.address.toLowerCase() === + token.address.toLowerCase() + : false + }
197-201
: Add keys for skeleton rowsPrevents React diff churn even if lint is suppressed.
- {props.isPending && - new Array(20).fill(0).map(() => ( - // biome-ignore lint/correctness/useJsxKeyInIterable: ok - <TokenButtonSkeleton /> - ))} + {props.isPending && + new Array(20).fill(0).map((_, i) => ( + <TokenButtonSkeleton key={i} /> + ))}packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx (5)
41-46
: Graceful default chain fallback when preferred chain not presentMirror buy-side fix to avoid undefined default when chainId 1 isn’t available.
- return chains.find((chain) => chain.chainId === (chainId || 1)); + return ( + chains.find((chain) => chain.chainId === (chainId || 1)) ?? chains[0] + );
247-276
: Reuse shared SearchInput to remove duplicated markupUse the shared SearchInput component for consistency and less DOM noise. Add the import and replace the inline block.
+import { SearchInput } from "./SearchInput.js";
- <Container px="md"> - <div - style={{ - position: "relative", - }} - > - <Container color="secondaryText"> - <MagnifyingGlassIcon - width={iconSize.md} - height={iconSize.md} - style={{ - position: "absolute", - left: spacing.sm, - top: "50%", - transform: "translateY(-50%)", - }} - /> - </Container> - - <Input - variant="outline" - placeholder="Search by Token or Address" - value={props.search} - style={{ - paddingLeft: "44px", - }} - onChange={(e) => props.setSearch(e.target.value)} - /> - </div> - </Container> + <Container px="md"> + <SearchInput + value={props.search} + onChange={props.setSearch} + placeholder="Search by Token or Address" + /> + </Container>Also applies to: 1-33
470-471
: Use locale-aware currency formattingImproves readability and i18n.
- {usdValue < 0.01 ? "~$0.00" : `$${usdValue.toFixed(2)}`} + {usdValue < 0.01 + ? "~$0.00" + : usdValue.toLocaleString(undefined, { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, + })}
281-289
: Consider virtualization for long listsHeight-locked, scrollable lists of balances can render many rows; virtualize to keep perf smooth on slower devices.
319-323
: Add keys for skeleton rowsSame nit as buy-side.
- {props.isPending && - new Array(20).fill(0).map(() => ( - // biome-ignore lint/correctness/useJsxKeyInIterable: ok - <TokenButtonSkeleton /> - ))} + {props.isPending && + new Array(20).fill(0).map((_, i) => ( + <TokenButtonSkeleton key={i} /> + ))}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (31)
packages/thirdweb/package.json
(1 hunks)packages/thirdweb/src/bridge/types/Chain.ts
(1 hunks)packages/thirdweb/src/pay/convert/type.ts
(1 hunks)packages/thirdweb/src/react/core/design-system/index.ts
(2 hunks)packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx
(2 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
(1 hunks)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
(1 hunks)packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/components/Img.tsx
(5 hunks)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx
(2 hunks)packages/thirdweb/src/react/web/ui/components/Spinner.tsx
(2 hunks)packages/thirdweb/src/react/web/ui/components/basic.tsx
(1 hunks)packages/thirdweb/src/react/web/ui/components/buttons.tsx
(3 hunks)packages/thirdweb/src/react/web/ui/components/formElements.tsx
(2 hunks)packages/thirdweb/src/react/web/ui/components/text.tsx
(2 hunks)packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
(1 hunks)packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
(1 hunks)packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
(1 hunks)packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
(1 hunks)packages/thirdweb/src/stories/BuyWidget.stories.tsx
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}
: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/types
or localtypes.ts
barrels
Prefer type aliases over interface except for nominal shapes
Avoidany
andunknown
unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial
,Pick
, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}
: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/types
where applicable
Prefertype
aliases overinterface
except for nominal shapes
Avoidany
andunknown
unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
packages/thirdweb/src/react/web/ui/components/Spinner.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx
packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx
packages/thirdweb/src/react/web/ui/components/text.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/react/web/ui/components/basic.tsx
packages/thirdweb/src/react/web/ui/components/Img.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
packages/thirdweb/src/react/core/design-system/index.ts
packages/thirdweb/src/bridge/types/Chain.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/react/web/ui/components/formElements.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
packages/thirdweb/src/pay/convert/type.ts
packages/thirdweb/src/react/web/ui/components/buttons.tsx
packages/thirdweb/src/react/web/ui/components/Skeleton.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
packages/thirdweb/src/react/web/ui/components/Spinner.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx
packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx
packages/thirdweb/src/react/web/ui/components/text.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/react/web/ui/components/basic.tsx
packages/thirdweb/src/react/web/ui/components/Img.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
packages/thirdweb/src/react/core/design-system/index.ts
packages/thirdweb/src/bridge/types/Chain.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/react/web/ui/components/formElements.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
packages/thirdweb/src/pay/convert/type.ts
packages/thirdweb/src/react/web/ui/components/buttons.tsx
packages/thirdweb/src/react/web/ui/components/Skeleton.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}
: Every public symbol must have comprehensive TSDoc with at least one compiling@example
and a custom tag (@beta
,@internal
,@experimental
, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf")
)
Files:
packages/thirdweb/src/react/web/ui/components/Spinner.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx
packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx
packages/thirdweb/src/react/web/ui/components/text.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/react/web/ui/components/basic.tsx
packages/thirdweb/src/react/web/ui/components/Img.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
packages/thirdweb/src/react/core/design-system/index.ts
packages/thirdweb/src/bridge/types/Chain.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/react/web/ui/components/formElements.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
packages/thirdweb/src/pay/convert/type.ts
packages/thirdweb/src/react/web/ui/components/buttons.tsx
packages/thirdweb/src/react/web/ui/components/Skeleton.tsx
**/*.stories.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
For new UI components, add Storybook stories (
*.stories.tsx
) alongside the code
Files:
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
**/types.ts
📄 CodeRabbit inference engine (AGENTS.md)
Provide and re‑use local type barrels in a
types.ts
file
Files:
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Track bundle budgets via
package.json#size-limit
Files:
packages/thirdweb/package.json
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Pull‑request titles must start with the affected workspace in brackets (e.g., [SDK], [Dashboard], [Portal], [Playground])
📚 Learning: 2025-07-02T20:04:53.982Z
Learnt from: MananTank
PR: thirdweb-dev/js#7505
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/webhook-metrics.tsx:40-40
Timestamp: 2025-07-02T20:04:53.982Z
Learning: The Spinner component imported from "@/components/ui/Spinner/Spinner" in the dashboard accepts a className prop, not a size prop. The correct usage is <Spinner className="size-4" />, not <Spinner size="sm" />. The component defaults to "size-4" if no className is provided.
Applied to files:
packages/thirdweb/src/react/web/ui/components/Spinner.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Anything that consumes hooks from `tanstack/react-query` or thirdweb SDKs.
Applied to files:
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
packages/thirdweb/src/react/web/ui/components/Img.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
packages/thirdweb/src/react/web/ui/components/Img.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Use React Query (`tanstack/react-query`) for all client data fetching.
Applied to files:
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts
📚 Learning: 2025-07-07T21:21:47.488Z
Learnt from: saminacodes
PR: thirdweb-dev/js#7543
File: apps/portal/src/app/pay/page.mdx:4-4
Timestamp: 2025-07-07T21:21:47.488Z
Learning: In the thirdweb-dev/js repository, lucide-react icons must be imported with the "Icon" suffix (e.g., ExternalLinkIcon, RocketIcon) as required by the new linting rule, contrary to the typical lucide-react convention of importing without the suffix.
Applied to files:
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.stories.tsx : Add Storybook stories (`*.stories.tsx`) alongside new UI components
Applied to files:
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to **/*.stories.tsx : For new UI components, add Storybook stories (`*.stories.tsx`) alongside the code
Applied to files:
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/components/*.{stories,test}.{tsx,ts} : Provide a Storybook story (`MyComponent.stories.tsx`) or unit test alongside the component.
Applied to files:
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
packages/thirdweb/src/stories/BuyWidget.stories.tsx
📚 Learning: 2025-05-29T10:49:52.981Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/ContractHeader.tsx:26-43
Timestamp: 2025-05-29T10:49:52.981Z
Learning: In React image components, conditional rendering of the entire image container (e.g., `{props.image && <Img />}`) serves a different purpose than fallback handling. The conditional prevents rendering any image UI when no image metadata exists, while the fallback prop handles cases where image metadata exists but the image fails to load. This pattern is intentional to distinguish between "no image intended" vs "image intended but failed to load".
Applied to files:
packages/thirdweb/src/react/web/ui/components/Img.tsx
📚 Learning: 2025-08-09T15:37:30.990Z
Learnt from: MananTank
PR: thirdweb-dev/js#7822
File: apps/dashboard/src/@/components/contracts/contract-card/contract-publisher.tsx:22-31
Timestamp: 2025-08-09T15:37:30.990Z
Learning: The Img component at apps/dashboard/src/@/components/blocks/Img.tsx properly handles empty string src values by immediately setting status to "fallback" and converting empty strings to undefined in the img element's src attribute, preventing unnecessary network requests.
Applied to files:
packages/thirdweb/src/react/web/ui/components/Img.tsx
📚 Learning: 2025-06-17T18:30:52.976Z
Learnt from: MananTank
PR: thirdweb-dev/js#7356
File: apps/nebula/src/app/not-found.tsx:1-1
Timestamp: 2025-06-17T18:30:52.976Z
Learning: In the thirdweb/js project, the React namespace is available for type annotations (like React.FC) without needing to explicitly import React. This is project-specific configuration that differs from typical TypeScript/React setups.
Applied to files:
packages/thirdweb/src/react/web/ui/components/Img.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests deterministic and side‑effect free; use Vitest
Applied to files:
packages/thirdweb/package.json
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests deterministic and side-effect free
Applied to files:
packages/thirdweb/package.json
📚 Learning: 2025-06-26T19:46:04.024Z
Learnt from: gregfromstl
PR: thirdweb-dev/js#7450
File: packages/thirdweb/src/bridge/Webhook.ts:57-81
Timestamp: 2025-06-26T19:46:04.024Z
Learning: In the onramp webhook schema (`packages/thirdweb/src/bridge/Webhook.ts`), the `currencyAmount` field is intentionally typed as `z.number()` while other amount fields use `z.string()` because `currencyAmount` represents fiat currency amounts in decimals (like $10.50), whereas other amount fields represent token amounts in wei (very large integers that benefit from bigint representation). The different naming convention (`currencyAmount` vs `amount`) reflects this intentional distinction.
Applied to files:
packages/thirdweb/src/pay/convert/type.ts
📚 Learning: 2025-08-28T19:32:53.229Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:50-60
Timestamp: 2025-08-28T19:32:53.229Z
Learning: The Button component in packages/ui/src/components/button.tsx has type="button" as the default when no type prop is explicitly provided. This means explicitly adding type="button" to Button components is redundant unless a different type (like "submit") is specifically needed.
Applied to files:
packages/thirdweb/src/react/web/ui/components/buttons.tsx
🧬 Code graph analysis (17)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx (5)
packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain
(42-42)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button
(26-157)packages/thirdweb/src/react/core/design-system/index.ts (3)
fontSize
(164-172)spacing
(174-185)iconSize
(197-206)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img
(12-126)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
cleanedChainName
(1-3)
packages/thirdweb/src/stories/Bridge/Swap/SelectBuyToken.stories.tsx (4)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx (2)
SelectBuyTokenUI
(105-240)SelectBuyToken
(41-103)packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain
(42-42)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidgetContainer
(65-84)packages/thirdweb/src/stories/utils.tsx (1)
storyClient
(15-17)
packages/thirdweb/src/react/web/ui/components/Img.tsx (3)
packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton
(10-28)packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container
(77-178)packages/thirdweb/src/react/core/design-system/index.ts (1)
radius
(187-195)
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx (4)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx (2)
SelectBridgeChain
(26-37)SelectBridgeChainUI
(39-118)packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain
(42-42)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidgetContainer
(65-84)packages/thirdweb/src/stories/utils.tsx (1)
storyClient
(15-17)
packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx (1)
packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text
(18-34)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (2)
packages/thirdweb/src/wallets/types.ts (1)
AppMetadata
(3-20)packages/thirdweb/src/bridge/types/Chain.ts (1)
Chain
(5-40)
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidget
(45-63)packages/thirdweb/src/stories/utils.tsx (1)
storyClient
(15-17)
packages/thirdweb/src/stories/BuyWidget.stories.tsx (1)
packages/thirdweb/src/stories/utils.tsx (1)
storyClient
(15-17)
packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
packages/thirdweb/src/react/core/design-system/index.ts (1)
Theme
(49-96)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (15)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (2)
ActiveWalletInfo
(137-141)SwapWidgetConnectOptions
(25-135)packages/thirdweb/src/react/core/design-system/index.ts (5)
Theme
(49-96)radius
(187-195)fontSize
(164-172)spacing
(174-185)iconSize
(197-206)packages/thirdweb/src/pay/convert/type.ts (2)
SupportedFiatCurrency
(27-27)getFiatSymbol
(29-34)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx (1)
SelectBuyToken
(41-103)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx (1)
SelectSellToken
(48-67)packages/thirdweb/src/exports/utils.ts (2)
toUnits
(39-39)toTokens
(39-39)packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container
(77-178)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button
(26-157)packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
Input
(36-106)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (1)
useBridgeChains
(5-12)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text
(18-34)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton
(10-28)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img
(12-126)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
cleanedChainName
(1-3)packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (1)
ArrowUpDownIcon
(3-23)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)
packages/thirdweb/src/exports/utils.ts (1)
isAddress
(148-148)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (3)
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container
(77-178)packages/thirdweb/src/react/core/design-system/index.ts (2)
iconSize
(197-206)spacing
(174-185)packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
Input
(36-106)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx (10)
packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain
(42-42)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (1)
useBridgeChains
(5-12)packages/thirdweb/src/react/web/ui/components/basic.tsx (3)
Container
(77-178)ModalHeader
(35-65)Line
(67-72)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (1)
SearchInput
(6-41)packages/thirdweb/src/react/core/design-system/index.ts (3)
spacing
(174-185)iconSize
(197-206)fontSize
(164-172)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text
(18-34)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton
(10-28)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button
(26-157)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img
(12-126)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
cleanedChainName
(1-3)
packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx (4)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx (3)
SelectSellToken
(48-67)SelectSellTokenConnectedUI
(190-362)SelectSellTokenDisconnectedUI
(69-120)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo
(137-141)packages/thirdweb/src/stories/utils.tsx (1)
storyClient
(15-17)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidgetContainer
(65-84)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-sell-token.tsx (12)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo
(137-141)packages/thirdweb/src/react/web/ui/components/basic.tsx (3)
Container
(77-178)ModalHeader
(35-65)Line
(67-72)packages/thirdweb/src/react/core/design-system/index.ts (4)
radius
(187-195)iconSize
(197-206)spacing
(174-185)fontSize
(164-172)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (1)
useBridgeChains
(5-12)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (2)
useTokenBalances
(71-115)TokenBalance
(39-57)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner
(11-36)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx (1)
SelectChainButton
(13-47)packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
Input
(36-106)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button
(26-157)packages/thirdweb/src/pay/convert/get-token.ts (1)
getToken
(6-37)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img
(12-126)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton
(10-28)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (15)
packages/thirdweb/src/react/core/design-system/index.ts (1)
Theme
(49-96)packages/thirdweb/src/pay/convert/type.ts (1)
SupportedFiatCurrency
(27-27)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
SwapWidgetConnectOptions
(25-135)packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx (1)
EmbedContainer
(441-466)packages/thirdweb/src/react/web/ui/components/DynamicHeight.tsx (1)
DynamicHeight
(8-33)packages/thirdweb/src/react/core/hooks/useBridgePrepare.ts (2)
BridgePrepareResult
(23-27)BridgePrepareRequest
(14-18)packages/thirdweb/src/react/web/ui/ConnectWallet/locale/getConnectLocale.ts (1)
useConnectLocale
(45-54)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (1)
useBridgeChains
(5-12)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner
(11-36)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (1)
SwapUI
(50-96)packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx (1)
QuoteLoader
(87-173)packages/thirdweb/src/exports/utils.ts (1)
toTokens
(39-39)packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx (1)
PaymentDetails
(57-398)packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx (1)
StepRunner
(66-404)packages/thirdweb/src/react/web/adapters/WindowAdapter.ts (1)
webWindowAdapter
(23-23)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx (13)
packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain
(42-42)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-bridge-chains.ts (1)
useBridgeChains
(5-12)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)
useTokens
(7-37)packages/thirdweb/src/react/web/ui/components/basic.tsx (3)
Container
(77-178)ModalHeader
(35-65)Line
(67-72)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner
(11-36)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx (1)
SelectChainButton
(13-47)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (1)
SearchInput
(6-41)packages/thirdweb/src/react/core/design-system/index.ts (4)
radius
(187-195)spacing
(174-185)fontSize
(164-172)iconSize
(197-206)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button
(26-157)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text
(18-34)packages/thirdweb/src/pay/convert/get-token.ts (1)
getToken
(6-37)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img
(12-126)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton
(10-28)
🪛 Gitleaks (8.27.2)
packages/thirdweb/src/stories/BuyWidget.stories.tsx
[high] 31-31: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Unit Tests
- GitHub Check: Lint Packages
- GitHub Check: Build Packages
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: Analyze (javascript)
export function getFiatSymbol(showBalanceInFiat: SupportedFiatCurrency) { | ||
switch (showBalanceInFiat) { | ||
case "USD": | ||
return "$"; | ||
case "CAD": | ||
return "$"; | ||
case "GBP": | ||
return "£"; | ||
case "EUR": | ||
return "€"; | ||
case "JPY": | ||
return "¥"; | ||
case "AUD": | ||
return "$"; | ||
case "NZD": | ||
return "$"; | ||
default: | ||
return "$"; | ||
if (currencySymbol[showBalanceInFiat]) { | ||
return currencySymbol[showBalanceInFiat]; | ||
} | ||
return "$"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type-safety bug: fallback is ill-typed and may be optimized away
currencySymbol
is Record<SupportedFiatCurrency, string>
. Since SupportedFiatCurrency
includes string & {}
, the index signature becomes Record<string, string>
, making currencySymbol[showBalanceInFiat]
always typed as string
(truthy), so the $
fallback is effectively dead by type, risking misuse elsewhere.
-export function getFiatSymbol(showBalanceInFiat: SupportedFiatCurrency) {
- if (currencySymbol[showBalanceInFiat]) {
- return currencySymbol[showBalanceInFiat];
- }
- return "$";
-}
+export function getFiatSymbol(showBalanceInFiat: SupportedFiatCurrency): string {
+ const symbol =
+ currencySymbol[
+ showBalanceInFiat as (typeof CURRENCIES)[number]
+ ];
+ return symbol ?? "$";
+}
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/thirdweb/src/pay/convert/type.ts around lines 29 to 34, the fallback
"$" is effectively dead because currencySymbol is typed as
Record<SupportedFiatCurrency, string> which (due to SupportedFiatCurrency
including string) makes the index always typed as string; fix by making the
lookup actually nullable/guarded and the mapping type narrower: change
currencySymbol's type to Partial<Record<SupportedFiatCurrency, string>> (or a
stricter literal-keyed type), then use an explicit undefined check or the
nullish coalescing operator to return currencySymbol[showBalanceInFiat] ?? "$"
(or use Object.prototype.hasOwnProperty.call(currencySymbol, showBalanceInFiat)
before returning) so the fallback can never be optimized away by the type
system.
const currencySymbol: Record<SupportedFiatCurrency, string> = { | ||
USD: "$", | ||
EUR: "€", | ||
GBP: "£", | ||
JPY: "¥", | ||
KRW: "₩", | ||
CNY: "¥", | ||
INR: "₹", | ||
NOK: "kr", | ||
SEK: "kr", | ||
CHF: "CHF", | ||
AUD: "$", | ||
CAD: "$", | ||
NZD: "$", | ||
MXN: "$", | ||
BRL: "R$", | ||
CLP: "$", | ||
CZK: "Kč", | ||
DKK: "kr", | ||
HKD: "$", | ||
HUF: "Ft", | ||
IDR: "Rp", | ||
ILS: "₪", | ||
ISK: "kr", | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Retype currencySymbol to cover only known codes
Scope the map to known CURRENCIES; keep it internal.
-const currencySymbol: Record<SupportedFiatCurrency, string> = {
+/**
+ * @internal
+ */
+const currencySymbol: Partial<Record<(typeof CURRENCIES)[number], string>> = {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const currencySymbol: Record<SupportedFiatCurrency, string> = { | |
USD: "$", | |
EUR: "€", | |
GBP: "£", | |
JPY: "¥", | |
KRW: "₩", | |
CNY: "¥", | |
INR: "₹", | |
NOK: "kr", | |
SEK: "kr", | |
CHF: "CHF", | |
AUD: "$", | |
CAD: "$", | |
NZD: "$", | |
MXN: "$", | |
BRL: "R$", | |
CLP: "$", | |
CZK: "Kč", | |
DKK: "kr", | |
HKD: "$", | |
HUF: "Ft", | |
IDR: "Rp", | |
ILS: "₪", | |
ISK: "kr", | |
}; | |
/** | |
* @internal | |
*/ | |
const currencySymbol: Partial<Record<(typeof CURRENCIES)[number], string>> = { | |
USD: "$", | |
EUR: "€", | |
GBP: "£", | |
JPY: "¥", | |
KRW: "₩", | |
CNY: "¥", | |
INR: "₹", | |
NOK: "kr", | |
SEK: "kr", | |
CHF: "CHF", | |
AUD: "$", | |
CAD: "$", | |
NZD: "$", | |
MXN: "$", | |
BRL: "R$", | |
CLP: "$", | |
CZK: "Kč", | |
DKK: "kr", | |
HKD: "$", | |
HUF: "Ft", | |
IDR: "Rp", | |
ILS: "₪", | |
ISK: "kr", | |
}; |
export function SelectBuyToken(props: SelectBuyTokenProps) { | ||
const activeChain = useActiveWalletChain(); | ||
const chainQuery = useBridgeChains(props.client); | ||
const [search, setSearch] = useState(""); | ||
const [limit, setLimit] = useState(1000); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add TSDoc and explicit return types for exported components (SDK public API requirement)
Public symbols under packages/thirdweb must include TSDoc (with @example and a custom tag) and explicit return types.
-export function SelectBuyToken(props: SelectBuyTokenProps) {
+/**
+ * Select a token to buy on the chosen bridge chain.
+ * @example
+ * <SelectBuyToken onBack={() => {}} client={client} selectedToken={token} setSelectedToken={setToken} />
+ * @beta
+ */
+export function SelectBuyToken(props: SelectBuyTokenProps): JSX.Element {
…
}
-export function SelectBuyTokenUI(
+/**
+ * Presentational UI for SelectBuyToken when data/state is managed externally.
+ * @example
+ * <SelectBuyTokenUI {...props} />
+ * @beta
+ */
+export function SelectBuyTokenUI(
props: SelectBuyTokenProps & {
tokens: Token[];
isPending: boolean;
selectedChain: BridgeChain | undefined;
setSelectedChain: (chain: BridgeChain) => void;
search: string;
setSearch: (search: string) => void;
selectedToken: TokenWithPrices | undefined;
setSelectedToken: (token: TokenWithPrices) => void;
showMore: (() => void) | undefined;
},
-) {
+): JSX.Element | null {
Also applies to: 105-117
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
around lines 41-46 (and also update the exported component occurrences at lines
105-117), the exported React component is missing TSDoc and an explicit return
type required by the SDK public API. Add a TSDoc block above the exported
function including a short description, an @example showing basic usage, and the
required custom tag (e.g., @thirdweb), then declare the function return type
explicitly as JSX.Element (or React.ReactElement) in its signature; apply the
same pattern to the other exported component(s) at lines 105-117. Ensure imports
already provide React types if needed.
<Button | ||
variant={props.isSelected ? "secondary" : "ghost-solid"} | ||
fullWidth | ||
style={{ | ||
justifyContent: "flex-start", | ||
fontWeight: 500, | ||
fontSize: fontSize.md, | ||
border: "1px solid transparent", | ||
padding: `${spacing.sm} ${spacing.sm}`, | ||
textAlign: "left", | ||
lineHeight: "1.5", | ||
height: "70px", | ||
}} | ||
gap="sm" | ||
onClick={async () => { | ||
setIsLoading(true); | ||
const tokenWithPrices = await getToken( | ||
props.client, | ||
props.token.address, | ||
props.token.chainId, | ||
); | ||
setIsLoading(false); | ||
props.onSelect(tokenWithPrices); | ||
}} | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix stuck-loading and unhandled errors on token fetch; also disable while loading
getToken can throw; isLoading never resets and the button stays interactive. Wrap in try/catch/finally, gate re-entrancy, and reflect state via disabled/aria.
- <Button
+ <Button
+ disabled={isLoading}
+ aria-busy={isLoading}
+ aria-selected={props.isSelected}
variant={props.isSelected ? "secondary" : "ghost-solid"}
fullWidth
@@
- onClick={async () => {
- setIsLoading(true);
- const tokenWithPrices = await getToken(
- props.client,
- props.token.address,
- props.token.chainId,
- );
- setIsLoading(false);
- props.onSelect(tokenWithPrices);
- }}
+ onClick={async () => {
+ if (isLoading) return;
+ setIsLoading(true);
+ try {
+ const tokenWithPrices = await getToken(
+ props.client,
+ props.token.address,
+ props.token.chainId,
+ );
+ props.onSelect(tokenWithPrices);
+ } catch (e) {
+ console.error("Failed to load token", e);
+ // TODO: surface a user-visible error/toast
+ } finally {
+ setIsLoading(false);
+ }
+ }}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<Button | |
variant={props.isSelected ? "secondary" : "ghost-solid"} | |
fullWidth | |
style={{ | |
justifyContent: "flex-start", | |
fontWeight: 500, | |
fontSize: fontSize.md, | |
border: "1px solid transparent", | |
padding: `${spacing.sm} ${spacing.sm}`, | |
textAlign: "left", | |
lineHeight: "1.5", | |
height: "70px", | |
}} | |
gap="sm" | |
onClick={async () => { | |
setIsLoading(true); | |
const tokenWithPrices = await getToken( | |
props.client, | |
props.token.address, | |
props.token.chainId, | |
); | |
setIsLoading(false); | |
props.onSelect(tokenWithPrices); | |
}} | |
> | |
<Button | |
disabled={isLoading} | |
aria-busy={isLoading} | |
aria-selected={props.isSelected} | |
variant={props.isSelected ? "secondary" : "ghost-solid"} | |
fullWidth | |
style={{ | |
justifyContent: "flex-start", | |
fontWeight: 500, | |
fontSize: fontSize.md, | |
border: "1px solid transparent", | |
padding: `${spacing.sm} ${spacing.sm}`, | |
textAlign: "left", | |
lineHeight: "1.5", | |
height: "70px", | |
}} | |
gap="sm" | |
onClick={async () => { | |
if (isLoading) return; | |
setIsLoading(true); | |
try { | |
const tokenWithPrices = await getToken( | |
props.client, | |
props.token.address, | |
props.token.chainId, | |
); | |
props.onSelect(tokenWithPrices); | |
} catch (e) { | |
console.error("Failed to load token", e); | |
// TODO: surface a user-visible error/toast | |
} finally { | |
setIsLoading(false); | |
} | |
}} | |
> |
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-buy-token.tsx
around lines 250–274, the onClick handler can throw and leaves isLoading true
while the button remains interactive; wrap the async logic in a
re-entrancy-guarded try/catch/finally: return early if already loading,
setIsLoading(true) before awaiting getToken, catch and handle/log the error, and
setIsLoading(false) in finally so loading always clears; also pass
disabled={isLoading} and aria-busy={isLoading} to the Button to prevent
interaction and expose state to assistive tech.
export function ChainButton(props: { | ||
chain: BridgeChain; | ||
client: ThirdwebClient; | ||
onClick: () => void; | ||
isSelected: boolean; | ||
}) { | ||
return ( | ||
<Button | ||
variant={props.isSelected ? "secondary" : "ghost-solid"} | ||
fullWidth | ||
style={{ | ||
justifyContent: "flex-start", | ||
fontWeight: 500, | ||
fontSize: fontSize.md, | ||
border: "1px solid transparent", | ||
padding: `${spacing.sm} ${spacing.sm}`, | ||
}} | ||
gap="sm" | ||
onClick={props.onClick} | ||
> | ||
<Img | ||
src={props.chain.icon} | ||
client={props.client} | ||
width={iconSize.lg} | ||
height={iconSize.lg} | ||
/> | ||
{cleanedChainName(props.chain.name)} | ||
</Button> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Missing TSDoc for exported UI components.
Public exports (SelectBridgeChain, SelectBridgeChainUI, ChainButton) need TSDoc with an @beta/@internal tag and a minimal example.
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx around
lines 136 to 165, the exported UI component ChainButton (and sibling exports
SelectBridgeChain and SelectBridgeChainUI) lack TSDoc; add a brief TSDoc block
above each exported component including a one-line description, a @beta or
@internal tag as appropriate, and a minimal usage example showing required props
(e.g., chain, client, isSelected, onClick). Keep the comments concise, use
consistent formatting with other files in the package, and ensure the examples
compile/type-check with current prop types.
return useQuery<Token[]>({ | ||
queryKey: ["tokens", options], | ||
enabled: !!options.chainId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Stabilize/serialize queryKey
Avoid placing the full options object (and non-serializable client) in the key; include primitives only.
- return useQuery<Token[]>({
- queryKey: ["tokens", options],
+ return useQuery<Token[]>({
+ queryKey: [
+ "tokens",
+ options.client.clientId,
+ options.chainId ?? null,
+ (options.search ?? "").toLowerCase(),
+ options.offset,
+ options.limit,
+ ],
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return useQuery<Token[]>({ | |
queryKey: ["tokens", options], | |
enabled: !!options.chainId, | |
return useQuery<Token[]>({ | |
queryKey: [ | |
"tokens", | |
options.client.clientId, | |
options.chainId ?? null, | |
(options.search ?? "").toLowerCase(), | |
options.offset, | |
options.limit, | |
], | |
enabled: !!options.chainId, |
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts around
lines 14–16, the queryKey currently includes the full options object (which may
contain non-serializable client instances) — replace it with a stable,
serializable key composed only of primitive values from options (e.g.,
options.chainId and any other relevant filter/search/page primitives) or a
deterministic string like JSON.stringify(pick(options,
['chainId','search','page'])) to ensure stability and avoid embedding
non-serializable objects in the key.
export type TokenBalance = { | ||
balance: string; | ||
chain_id: number; | ||
decimals: 1; | ||
name: string; | ||
icon_uri: string; | ||
price_data: { | ||
circulating_supply: number; | ||
market_cap_usd: number; | ||
percent_change_24h: number; | ||
price_timestamp: string; | ||
price_usd: number; | ||
total_supply: number; | ||
usd_value: number; | ||
volume_24h_usd: number; | ||
}; | ||
symbol: string; | ||
token_address: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect literal type for decimals
decimals: 1
makes the field literally type 1. It must be number
.
export type TokenBalance = {
balance: string;
chain_id: number;
- decimals: 1;
+ decimals: number;
name: string;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export type TokenBalance = { | |
balance: string; | |
chain_id: number; | |
decimals: 1; | |
name: string; | |
icon_uri: string; | |
price_data: { | |
circulating_supply: number; | |
market_cap_usd: number; | |
percent_change_24h: number; | |
price_timestamp: string; | |
price_usd: number; | |
total_supply: number; | |
usd_value: number; | |
volume_24h_usd: number; | |
}; | |
symbol: string; | |
token_address: string; | |
}; | |
export type TokenBalance = { | |
balance: string; | |
chain_id: number; | |
decimals: number; | |
name: string; | |
icon_uri: string; | |
price_data: { | |
circulating_supply: number; | |
market_cap_usd: number; | |
percent_change_24h: number; | |
price_timestamp: string; | |
price_usd: number; | |
total_supply: number; | |
usd_value: number; | |
volume_24h_usd: number; | |
}; | |
symbol: string; | |
token_address: string; | |
}; |
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts around
lines 39 to 57, the TokenBalance type incorrectly declares decimals as the
literal type `1`; change the type to `number` (i.e., `decimals: number`) so
decimals can represent any numeric precision and update any related type
imports/usages if necessary to reflect the widened type.
export function useTokenBalances(options: { | ||
clientId: string; | ||
page: number; | ||
limit: number; | ||
walletAddress: string; | ||
chainId: number | undefined; | ||
}) { | ||
return useQuery({ | ||
queryKey: ["bridge/v1/wallets", options], | ||
enabled: !!options.chainId, | ||
queryFn: async () => { | ||
if (!options.chainId) { | ||
throw new Error("Chain ID is required"); | ||
} | ||
const url = new URL( | ||
`https://api.thirdweb.com/v1/wallets/${options.walletAddress}/tokens`, | ||
); | ||
url.searchParams.set("chainId", options.chainId.toString()); | ||
url.searchParams.set("limit", options.limit.toString()); | ||
url.searchParams.set("page", options.page.toString()); | ||
url.searchParams.set("metadata", "true"); | ||
url.searchParams.set("resolveMetadataLinks", "true"); | ||
url.searchParams.set("includeSpam", "false"); | ||
url.searchParams.set("includeNative", "true"); | ||
url.searchParams.set("sortBy", "usd_value"); | ||
url.searchParams.set("sortOrder", "desc"); | ||
url.searchParams.set("includeWithoutPrice", "false"); // filter out tokens with no price | ||
|
||
const response = await fetch(url.toString(), { | ||
headers: { | ||
"x-client-id": options.clientId, | ||
}, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error( | ||
`Failed to fetch token balances: ${response.statusText}`, | ||
); | ||
} | ||
|
||
const json = (await response.json()) as TokenBalancesResponse; | ||
return json.result; | ||
}, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Harden balances query: key, enable, and abort
- Include only primitives in queryKey.
- Guard on walletAddress presence.
- Plumb AbortSignal to fetch to support cancellations.
-export function useTokenBalances(options: {
+export function useTokenBalances(options: {
clientId: string;
page: number;
limit: number;
walletAddress: string;
chainId: number | undefined;
}) {
- return useQuery({
- queryKey: ["bridge/v1/wallets", options],
- enabled: !!options.chainId,
- queryFn: async () => {
+ return useQuery({
+ queryKey: [
+ "bridge/v1/wallets/tokens",
+ options.clientId,
+ options.walletAddress.toLowerCase(),
+ options.chainId ?? null,
+ options.page,
+ options.limit,
+ ],
+ enabled: !!options.chainId && !!options.walletAddress,
+ queryFn: async ({ signal }) => {
if (!options.chainId) {
throw new Error("Chain ID is required");
}
const url = new URL(
`https://api.thirdweb.com/v1/wallets/${options.walletAddress}/tokens`,
);
@@
- const response = await fetch(url.toString(), {
+ const response = await fetch(url.toString(), {
+ signal,
headers: {
"x-client-id": options.clientId,
},
});
@@
- const json = (await response.json()) as TokenBalancesResponse;
- return json.result;
+ const json = (await response.json()) as TokenBalancesResponse;
+ return json.result;
},
});
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export function useTokenBalances(options: { | |
clientId: string; | |
page: number; | |
limit: number; | |
walletAddress: string; | |
chainId: number | undefined; | |
}) { | |
return useQuery({ | |
queryKey: ["bridge/v1/wallets", options], | |
enabled: !!options.chainId, | |
queryFn: async () => { | |
if (!options.chainId) { | |
throw new Error("Chain ID is required"); | |
} | |
const url = new URL( | |
`https://api.thirdweb.com/v1/wallets/${options.walletAddress}/tokens`, | |
); | |
url.searchParams.set("chainId", options.chainId.toString()); | |
url.searchParams.set("limit", options.limit.toString()); | |
url.searchParams.set("page", options.page.toString()); | |
url.searchParams.set("metadata", "true"); | |
url.searchParams.set("resolveMetadataLinks", "true"); | |
url.searchParams.set("includeSpam", "false"); | |
url.searchParams.set("includeNative", "true"); | |
url.searchParams.set("sortBy", "usd_value"); | |
url.searchParams.set("sortOrder", "desc"); | |
url.searchParams.set("includeWithoutPrice", "false"); // filter out tokens with no price | |
const response = await fetch(url.toString(), { | |
headers: { | |
"x-client-id": options.clientId, | |
}, | |
}); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to fetch token balances: ${response.statusText}`, | |
); | |
} | |
const json = (await response.json()) as TokenBalancesResponse; | |
return json.result; | |
}, | |
}); | |
} | |
export function useTokenBalances(options: { | |
clientId: string; | |
page: number; | |
limit: number; | |
walletAddress: string; | |
chainId: number | undefined; | |
}) { | |
return useQuery({ | |
queryKey: [ | |
"bridge/v1/wallets/tokens", | |
options.clientId, | |
options.walletAddress.toLowerCase(), | |
options.chainId ?? null, | |
options.page, | |
options.limit, | |
], | |
enabled: !!options.chainId && !!options.walletAddress, | |
queryFn: async ({ signal }) => { | |
if (!options.chainId) { | |
throw new Error("Chain ID is required"); | |
} | |
const url = new URL( | |
`https://api.thirdweb.com/v1/wallets/${options.walletAddress}/tokens`, | |
); | |
url.searchParams.set("chainId", options.chainId.toString()); | |
url.searchParams.set("limit", options.limit.toString()); | |
url.searchParams.set("page", options.page.toString()); | |
url.searchParams.set("metadata", "true"); | |
url.searchParams.set("resolveMetadataLinks", "true"); | |
url.searchParams.set("includeSpam", "false"); | |
url.searchParams.set("includeNative", "true"); | |
url.searchParams.set("sortBy", "usd_value"); | |
url.searchParams.set("sortOrder", "desc"); | |
url.searchParams.set("includeWithoutPrice", "false"); // filter out tokens with no price | |
const response = await fetch(url.toString(), { | |
signal, | |
headers: { | |
"x-client-id": options.clientId, | |
}, | |
}); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to fetch token balances: ${response.statusText}`, | |
); | |
} | |
const json = (await response.json()) as TokenBalancesResponse; | |
return json.result; | |
}, | |
}); | |
} |
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts around
lines 71 to 115, the query needs hardening: replace the complex object in
queryKey with only primitives (e.g. ["bridge/v1/wallets", clientId,
walletAddress, chainId, page, limit]), change enabled to require both chainId
and walletAddress (e.g. Boolean(options.chainId && options.walletAddress)),
guard early in queryFn to throw or return if walletAddress is missing, and
accept the react-query context signal (queryFn: async ({ signal }) => { ... })
and pass that AbortSignal into fetch options so the request can be cancelled.
@@ -2,6 +2,8 @@ | |||
import { useState } from "react"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reset status on src changes to avoid stale “loaded” state
When src changes, status can remain “loaded”, suppressing skeleton. Reset to “pending” for non-empty src.
-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
const propSrc = props.src;
+ useEffect(() => {
+ if (propSrc) {
+ setStatus("pending");
+ }
+ }, [propSrc]);
Also applies to: 35-36
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/components/Img.tsx around line 2 (and also
apply to lines 35-36), the component keeps a stale "loaded" status when the src
prop changes; update the component to reset status to "pending" whenever the src
prop changes to a non-empty value. Implement a useEffect that watches src and,
if src is truthy and different from previous value, sets status to "pending" (do
the same reset where status is managed on lines 35-36), ensuring the skeleton
shows for the new image load.
import { | ||
type SelectSellToken, | ||
SelectSellTokenConnectedUI, | ||
SelectSellTokenDisconnectedUI, | ||
} from "../../../react/web/ui/Bridge/swap-widget/select-sell-token.js"; | ||
import type { ActiveWalletInfo } from "../../../react/web/ui/Bridge/swap-widget/types.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type-only import breaks Meta.
You’re using the type-imported SelectSellToken in a value position (typeof). Import the value.
-import {
- type SelectSellToken,
- SelectSellTokenConnectedUI,
- SelectSellTokenDisconnectedUI,
-} from "../../../react/web/ui/Bridge/swap-widget/select-sell-token.js";
+import {
+ SelectSellToken,
+ SelectSellTokenConnectedUI,
+ SelectSellTokenDisconnectedUI,
+} from "../../../react/web/ui/Bridge/swap-widget/select-sell-token.js";
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
import { | |
type SelectSellToken, | |
SelectSellTokenConnectedUI, | |
SelectSellTokenDisconnectedUI, | |
} from "../../../react/web/ui/Bridge/swap-widget/select-sell-token.js"; | |
import type { ActiveWalletInfo } from "../../../react/web/ui/Bridge/swap-widget/types.js"; | |
import { | |
SelectSellToken, | |
SelectSellTokenConnectedUI, | |
SelectSellTokenDisconnectedUI, | |
} from "../../../react/web/ui/Bridge/swap-widget/select-sell-token.js"; | |
import type { ActiveWalletInfo } from "../../../react/web/ui/Bridge/swap-widget/types.js"; |
🤖 Prompt for AI Agents
In packages/thirdweb/src/stories/Bridge/Swap/SelectSellToken.stories.tsx around
lines 8 to 13, the SelectSellToken is imported as a type-only import but later
used in a value position (typeof SelectSellToken) which breaks Meta<typeof
SelectSellToken>; change the import so SelectSellToken is imported as a runtime
value (remove the "type" keyword) from
"../../../react/web/ui/Bridge/swap-widget/select-sell-token.js" while keeping
the other imports intact so Meta<typeof SelectSellToken> resolves correctly.
} | ||
|
||
const currencySymbol: Record<SupportedFiatCurrency, string> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we actually use this? i think we can just use the built in currency formatter no?
PR-Codex overview
This PR focuses on enhancing the
Bridge
functionality within thethirdweb
package, improving UI components, adding new features, and refining existing code for better usability and performance.Detailed summary
BridgeChain
type inChain.ts
.cleanedChainName
function inutils.ts
.DynamicHeight
component's transition effect.Text
component to includemultiline
prop.skeletonBg
color in design system.full
radius option in design system.Skeleton
component to accept additional styles.Line
component to allow dashed variants.style
prop toSpinner
component.useBridgeChains
hook for fetching bridge chains.Input
component to accept background color.ArrowUpDownIcon
component.UnsupportedTokenScreen
.SwapWidget
andBuyWidget
.getFiatSymbol
function for better currency handling.trackingTight
prop toText
component.Button
component.SearchInput
component for better token selection.SwapWidget
.SelectBuyToken
andSelectSellToken
components for better UX.Summary by CodeRabbit
New Features
Style/Improvements
Documentation
Chores