diff --git a/.tickets/tic-32f9.md b/.tickets/tic-32f9.md new file mode 100644 index 00000000..e0f26e01 --- /dev/null +++ b/.tickets/tic-32f9.md @@ -0,0 +1,43 @@ +--- +id: tic-32f9 +status: open +type: bug +priority: 1 +assignee: Jibles +created: 2026-03-19T09:48:16.870745261Z +--- +# USDC swap reverts after approval on Arbitrum + +## Summary + +Swapping USDC -> ETH on Arbitrum fails at the "Sign swap transaction" step after the approval step succeeds. ETH -> USDC swaps work fine. + +## Steps to Reproduce + +1. Connect with embedded wallet (Dynamic) on Arbitrum +2. Ask agent: "Swap 1 USDC to ETH on Arbitrum" +3. Confirm the Dynamic transaction modal +4. Observe: steps 1-3 (Getting swap quote, Switch to arbitrum, Approve token spending) succeed +5. Step 4 (Sign swap transaction) fails + +## Error + +`Execution failed: Swap failed: Transaction will revert: Execution reverted for an unknown reason.` + +## Expected Behavior + +Swap should execute successfully like ETH -> USDC does. + +## Notes + +- The approval tx goes through on-chain, so the user's USDC allowance is set but the trade never executes +- This appears to be specific to selling ERC-20 tokens (native ETH sells work) +- Could be a slippage, routing, or contract interaction issue +- Tested with wallet `0xD32Bb617c25f33563817A58004F2A441Ff8660F3` holding ~2.32 USDC on Arbitrum + +## Acceptance Criteria + +- [ ] USDC -> ETH swap executes successfully on Arbitrum +- [ ] Other ERC-20 -> native token swaps work +- [ ] No orphaned approvals left on failed swaps + diff --git a/.tickets/tic-616a.md b/.tickets/tic-616a.md new file mode 100644 index 00000000..d356c1c1 --- /dev/null +++ b/.tickets/tic-616a.md @@ -0,0 +1,39 @@ +--- +id: tic-616a +status: open +type: bug +priority: 2 +assignee: Jibles +created: 2026-03-19T09:48:28.499128674Z +--- +# Dollar sign stripped from chat input messages + +## Summary + +The `$` character is silently stripped from chat messages when sent. Typing `$2 worth of ETH` results in the message appearing as `worth of ETH` — the `$2` portion is completely removed. + +## Impact + +This makes the USD-based swap tool (`initiateSwapUsdTool`) effectively unusable, since users cannot specify dollar amounts. The agent then misinterprets the message (e.g., "1 worth is ambiguous"). + +## Steps to Reproduce + +1. Connect wallet and open a new chat +2. Type: `Swap $2 worth of ETH to USDC on Arbitrum` +3. Send the message +4. Observe: the message bubble shows `Swap worth of ETH to USDC on Arbitrum` — `$2` is gone + +## Expected Behavior + +The `$` character should be preserved in messages. Dollar amounts are a core part of the crypto trading UX. + +## Likely Location + +Chat input component text handling — possibly overzealous sanitization or a template literal issue stripping `$`. + +## Acceptance Criteria + +- [ ] Dollar sign characters are preserved in sent messages +- [ ] USD-based swap requests like 'Swap $2 of ETH to USDC' work correctly +- [ ] No XSS or injection regressions from allowing $ + diff --git a/.tickets/tic-78e5.md b/.tickets/tic-78e5.md new file mode 100644 index 00000000..aa80c0c2 --- /dev/null +++ b/.tickets/tic-78e5.md @@ -0,0 +1,33 @@ +--- +id: tic-78e5 +status: open +type: feature +priority: 3 +assignee: Jibles +created: 2026-03-19T09:48:42.234098894Z +--- +# External address lookup does not support ENS names + +## Summary + +The `lookupExternalAddressTool` only accepts raw hex Ethereum addresses. ENS names like `vitalik.eth` are rejected with "invalid format for direct lookup." + +## Current Behavior + +Asking "Look up the balance of vitalik.eth" returns: +> The address "vitalik.eth" couldn't be resolved (invalid format for direct lookup). Provide the resolved Ethereum address (e.g., 0xd8dA6BF...) to check its portfolio balance. + +## Expected Behavior + +The tool should resolve ENS names to addresses before performing the lookup, or the agent should resolve ENS first and then call the tool with the hex address. + +## Notes + +- This is a common user expectation — ENS names are widely used +- Resolution could happen server-side in the tool or client-side before the tool call + +## Acceptance Criteria + +- [ ] ENS names (e.g., vitalik.eth) resolve to addresses and return balances +- [ ] Invalid ENS names return a clear error message + diff --git a/.tickets/tic-93f0.md b/.tickets/tic-93f0.md new file mode 100644 index 00000000..ba7f4a4a --- /dev/null +++ b/.tickets/tic-93f0.md @@ -0,0 +1,34 @@ +--- +id: tic-93f0 +status: open +type: chore +priority: 4 +assignee: Jibles +created: 2026-03-19T09:49:10.195588513Z +--- +# Generic error messages for backend failures lack actionable detail + +## Summary + +When the backend API is unavailable, multiple tools show one of two generic error messages with no actionable detail: + +1. Portfolio tool: "Failed to fetch portfolio details" (red text) +2. All other wallet tools: "Something went wrong — The service is temporarily unavailable. Please try again." (red error banner) + +Neither message helps the user understand what's wrong or what to do beyond "try again." + +## Affected Tools + +All wallet-dependent tools when backend is down: portfolioTool, receiveTool, transactionHistoryTool, and likely others. + +## Suggested Improvement + +- Differentiate between network errors (backend unreachable), auth errors (session expired), and API errors (bad request) +- For backend-down scenarios, surface something like "Unable to reach the ShapeShift API. Check your connection or try again in a moment." +- Consider adding retry logic for transient failures + +## Acceptance Criteria + +- [ ] Error messages distinguish between different failure types +- [ ] Users get actionable guidance when errors occur + diff --git a/.tickets/tic-fc5f.md b/.tickets/tic-fc5f.md new file mode 100644 index 00000000..e3689eef --- /dev/null +++ b/.tickets/tic-fc5f.md @@ -0,0 +1,36 @@ +--- +id: tic-fc5f +status: open +type: bug +priority: 3 +assignee: Jibles +created: 2026-03-19T09:48:57.995716495Z +--- +# Portfolio side panel shows stale 'No assets found' state + +## Summary + +The portfolio side panel (opened by clicking the wallet address button in the nav bar) shows "\$0.00" and "No assets found — Connect a wallet to view your portfolio" even when the wallet IS connected and the portfolio tool returns real balances (\$8.80 USDC + ETH on Arbitrum). + +## Additional Issue + +The panel occasionally auto-opens during interactions (e.g., when scrolling, or when the wallet button region is inadvertently activated), stealing focus from the chat. This is disruptive during conversations with the agent. + +## Steps to Reproduce + +1. Connect embedded wallet (0xD32B...60F3) +2. Click the wallet address button in the top-right nav +3. Observe: panel shows \$0.00 and "No assets found" +4. Ask the agent "What is my portfolio balance?" +5. Agent correctly returns \$8.80 — the panel data is stale/wrong + +## Expected Behavior + +- Panel should show real portfolio balances matching what the portfolio tool returns +- Panel should not auto-open unexpectedly + +## Acceptance Criteria + +- [ ] Portfolio panel shows correct balances when wallet is connected +- [ ] Panel does not auto-open unexpectedly during chat interactions + diff --git a/apps/agentic-chat/src/components/Portfolio/PortfolioAssetList.tsx b/apps/agentic-chat/src/components/Portfolio/PortfolioAssetList.tsx index e644ef3d..f48e7f93 100644 --- a/apps/agentic-chat/src/components/Portfolio/PortfolioAssetList.tsx +++ b/apps/agentic-chat/src/components/Portfolio/PortfolioAssetList.tsx @@ -1,3 +1,5 @@ +import { useDynamicContext } from '@dynamic-labs/sdk-react-core' +import { Loader2, RefreshCw } from 'lucide-react' import { useMemo } from 'react' import { Virtuoso } from 'react-virtuoso' @@ -25,11 +27,20 @@ export function AssetListSkeleton({ rows = 5 }: { rows?: number }) { } export function PortfolioAssetList() { - const { assets, isLoading } = usePortfolioQuery() + const { primaryWallet } = useDynamicContext() + const { assets, isLoading, isFetching } = usePortfolioQuery() const groupedAssets = useMemo(() => groupPortfolioAssets(assets), [assets]) if (isLoading) { - return + return ( +
+
+ + Loading portfolio... +
+ +
+ ) } if (groupedAssets.length === 0) { @@ -37,21 +48,30 @@ export function PortfolioAssetList() {
No assets found
-
Connect a wallet to view your portfolio
+
+ {primaryWallet ? 'This wallet has no token balances' : 'Connect a wallet to view your portfolio'} +
) } return ( - ( -
- +
+ {isFetching && !isLoading && ( +
+
)} - /> + ( +
+ +
+ )} + /> +
) } diff --git a/apps/agentic-chat/src/components/Portfolio/PortfolioHeader.tsx b/apps/agentic-chat/src/components/Portfolio/PortfolioHeader.tsx index cde57027..35b25d76 100644 --- a/apps/agentic-chat/src/components/Portfolio/PortfolioHeader.tsx +++ b/apps/agentic-chat/src/components/Portfolio/PortfolioHeader.tsx @@ -1,4 +1,4 @@ -import { Check, Copy, ExternalLink } from 'lucide-react' +import { Check, Copy, ExternalLink, Loader2, RefreshCw } from 'lucide-react' import { useMemo } from 'react' import { toast } from 'sonner' @@ -26,8 +26,13 @@ type DeployedChain = { } export function PortfolioHeader({ isVaultMode }: PortfolioHeaderProps) { - const { totalBalance: walletBalance, delta24h, isLoading: isWalletLoading } = usePortfolioQuery() - const { totalBalance: vaultBalance, isLoading: isVaultLoading } = useVaultBalances() + const { + totalBalance: walletBalance, + delta24h, + isLoading: isWalletLoading, + isFetching: isWalletFetching, + } = usePortfolioQuery() + const { totalBalance: vaultBalance, isLoading: isVaultLoading, isFetching: isVaultFetching } = useVaultBalances() const { safeAddress, isDeployed, deployedChainIds, safeDeploymentState } = useSafeAccount() const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) @@ -57,21 +62,27 @@ export function PortfolioHeader({ isVaultMode }: PortfolioHeaderProps) { }, [deployedChainIds, safeDeploymentState]) const isLoading = isVaultMode ? isVaultLoading : isWalletLoading + const isFetching = isVaultMode ? isVaultFetching : isWalletFetching const displayBalance = isVaultMode ? vaultBalance : walletBalance if (isLoading) { return (
- - + + +
+ + Fetching balances... +
) } return (
-
+
+ {isFetching && !isLoading && }
{!isVaultMode && delta24h && ( diff --git a/apps/agentic-chat/src/hooks/usePortfolioQuery.ts b/apps/agentic-chat/src/hooks/usePortfolioQuery.ts index 86638fb7..8c0f21fc 100644 --- a/apps/agentic-chat/src/hooks/usePortfolioQuery.ts +++ b/apps/agentic-chat/src/hooks/usePortfolioQuery.ts @@ -1,7 +1,7 @@ import { isEthereumWallet } from '@dynamic-labs/ethereum' import { useDynamicContext } from '@dynamic-labs/sdk-react-core' import { isSolanaWallet } from '@dynamic-labs/solana' -import { useQuery } from '@tanstack/react-query' +import { keepPreviousData, useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { bnOrZero } from '@/lib/bignumber' @@ -24,8 +24,9 @@ export function usePortfolioQuery() { enabled: !!address, refetchInterval: REFETCH_INTERVAL, refetchOnWindowFocus: true, - staleTime: 10_000, - gcTime: 5 * 60 * 1000, + staleTime: 30_000, + gcTime: 30 * 60 * 1000, + placeholderData: keepPreviousData, }) const assets = useMemo(() => { diff --git a/apps/agentic-chat/src/hooks/useVaultBalances.ts b/apps/agentic-chat/src/hooks/useVaultBalances.ts index 317e136a..79511402 100644 --- a/apps/agentic-chat/src/hooks/useVaultBalances.ts +++ b/apps/agentic-chat/src/hooks/useVaultBalances.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query' +import { keepPreviousData, useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useSafeAccount } from '@/hooks/useSafeAccount' @@ -62,8 +62,9 @@ export function useVaultBalances() { enabled: isDeployed && deployedChains.length > 0, refetchInterval: REFETCH_INTERVAL, refetchOnWindowFocus: true, - staleTime: 10_000, - gcTime: 5 * 60 * 1000, + staleTime: 30_000, + gcTime: 30 * 60 * 1000, + placeholderData: keepPreviousData, }) const balances = useMemo(() => {