Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .tickets/tic-32f9.md
Original file line number Diff line number Diff line change
@@ -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

39 changes: 39 additions & 0 deletions .tickets/tic-616a.md
Original file line number Diff line number Diff line change
@@ -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 $

33 changes: 33 additions & 0 deletions .tickets/tic-78e5.md
Original file line number Diff line number Diff line change
@@ -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

34 changes: 34 additions & 0 deletions .tickets/tic-93f0.md
Original file line number Diff line number Diff line change
@@ -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

36 changes: 36 additions & 0 deletions .tickets/tic-fc5f.md
Original file line number Diff line number Diff line change
@@ -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

40 changes: 30 additions & 10 deletions apps/agentic-chat/src/components/Portfolio/PortfolioAssetList.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -25,33 +27,51 @@ 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 <AssetListSkeleton />
return (
<div className="flex flex-col">
<div className="flex items-center justify-center gap-2 py-3">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
<span className="text-sm text-muted-foreground">Loading portfolio...</span>
</div>
<AssetListSkeleton />
</div>
)
}

if (groupedAssets.length === 0) {
return (
<div className="flex items-center justify-center py-12 px-4">
<div className="text-center">
<div className="text-lg font-medium text-foreground">No assets found</div>
<div className="text-sm text-muted-foreground mt-1">Connect a wallet to view your portfolio</div>
<div className="text-sm text-muted-foreground mt-1">
{primaryWallet ? 'This wallet has no token balances' : 'Connect a wallet to view your portfolio'}
</div>
</div>
</div>
)
}

return (
<Virtuoso
style={{ height: '100%' }}
data={groupedAssets}
itemContent={(_index, group) => (
<div className="px-4 mb-2">
<GroupedAssetRow key={group.primaryAsset.assetId} group={group} />
<div className="relative h-full">
{isFetching && !isLoading && (
<div className="absolute top-2 right-4 z-10">
<RefreshCw className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
</div>
)}
/>
<Virtuoso
style={{ height: '100%' }}
data={groupedAssets}
itemContent={(_index, group) => (
<div className="px-4 mb-2">
<GroupedAssetRow key={group.primaryAsset.assetId} group={group} />
</div>
)}
/>
</div>
)
}
23 changes: 17 additions & 6 deletions apps/agentic-chat/src/components/Portfolio/PortfolioHeader.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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 (
<div className="flex flex-col items-center py-6 px-4 space-y-2">
<Skeleton className="h-10 w-48" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-12 w-48" />
<Skeleton className="h-3.5 w-32" />
<div className="flex items-center gap-2 pt-1">
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
<span className="text-xs text-muted-foreground">Fetching balances...</span>
</div>
</div>
)
}

return (
<div className="flex flex-col items-center py-6 px-4">
<div className="text-[40px] font-semibold tracking-tight text-foreground">
<div className="flex items-center gap-2 text-[40px] font-semibold tracking-tight text-foreground">
<Amount.Fiat value={displayBalance} />
{isFetching && !isLoading && <RefreshCw className="h-4 w-4 animate-spin text-muted-foreground" />}
</div>

{!isVaultMode && delta24h && (
Expand Down
7 changes: 4 additions & 3 deletions apps/agentic-chat/src/hooks/usePortfolioQuery.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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(() => {
Expand Down
7 changes: 4 additions & 3 deletions apps/agentic-chat/src/hooks/useVaultBalances.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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(() => {
Expand Down
Loading