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
15 changes: 11 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ UNCHAINED_ARBITRUM_NOVA_HTTP_URL=https://dev-api.arbitrum-nova.shapeshift.com
UNCHAINED_BASE_HTTP_URL=https://dev-api.base.shapeshift.com
UNCHAINED_SOLANA_HTTP_URL=https://dev-api.solana.shapeshift.com

# Solana RPC endpoint
# VITE_ prefix required for client access, but also used by server
# RPC endpoints (VITE_ prefix required for client access)
VITE_ETHEREUM_NODE_URL=https://api.ethereum.shapeshift.com/api/v1/jsonrpc
VITE_AVALANCHE_NODE_URL=https://api.avalanche.shapeshift.com/api/v1/jsonrpc
VITE_OPTIMISM_NODE_URL=https://api.optimism.shapeshift.com/api/v1/jsonrpc
VITE_BNBSMARTCHAIN_NODE_URL=https://api.bnbsmartchain.shapeshift.com/api/v1/jsonrpc
VITE_POLYGON_NODE_URL=https://api.polygon.shapeshift.com/api/v1/jsonrpc
VITE_GNOSIS_NODE_URL=https://daemon.gnosis.shapeshift.com
VITE_ARBITRUM_NODE_URL=https://api.arbitrum.shapeshift.com/api/v1/jsonrpc
VITE_BASE_NODE_URL=https://api.base.shapeshift.com/api/v1/jsonrpc
VITE_SOLANA_RPC_URL=https://api.solana.shapeshift.com/api/v1/jsonrpc

VITE_AGENTIC_SERVER_BASE_URL=http://localhost:4111

# Reown cloud app ID
VITE_PROJECT_ID=f3cf11a65c781d323d57f04018bccbe5
# Dynamic environment ID
VITE_DYNAMIC_ENVIRONMENT_ID=your-environment-id-here

VITE_FEATURE_ENABLE_SIDEBAR_LEFT=true
14 changes: 11 additions & 3 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ UNCHAINED_ARBITRUM_NOVA_HTTP_URL=https://api.arbitrum-nova.shapeshift.com
UNCHAINED_BASE_HTTP_URL=https://api.base.shapeshift.com
UNCHAINED_SOLANA_HTTP_URL=https://api.solana.shapeshift.com

# Solana RPC endpoint (VITE_ prefix required for client, also used by server)
# RPC endpoints (VITE_ prefix required for client)
VITE_ETHEREUM_NODE_URL=https://api.ethereum.shapeshift.com/api/v1/jsonrpc
VITE_AVALANCHE_NODE_URL=https://api.avalanche.shapeshift.com/api/v1/jsonrpc
VITE_OPTIMISM_NODE_URL=https://api.optimism.shapeshift.com/api/v1/jsonrpc
VITE_BNBSMARTCHAIN_NODE_URL=https://api.bnbsmartchain.shapeshift.com/api/v1/jsonrpc
VITE_POLYGON_NODE_URL=https://api.polygon.shapeshift.com/api/v1/jsonrpc
VITE_GNOSIS_NODE_URL=https://daemon.gnosis.shapeshift.com
VITE_ARBITRUM_NODE_URL=https://api.arbitrum.shapeshift.com/api/v1/jsonrpc
VITE_BASE_NODE_URL=https://api.base.shapeshift.com/api/v1/jsonrpc
VITE_SOLANA_RPC_URL=https://api.solana.shapeshift.com/api/v1/jsonrpc

# Reown cloud app ID
VITE_PROJECT_ID=f3cf11a65c781d323d57f04018bccbe5
# Dynamic environment ID (will be overridden by Vercel environment variables)
VITE_DYNAMIC_ENVIRONMENT_ID=c9215a6a-bcf2-4045-bb81-3f450853da66

# Feature flags (production settings)
VITE_FEATURE_ENABLE_SIDEBAR_LEFT=true
10 changes: 6 additions & 4 deletions apps/agentic-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
},
"dependencies": {
"@ai-sdk/react": "^2.0.76",
"@dynamic-labs/ethereum": "^4.49.0",
"@dynamic-labs/ethereum-core": "^4.49.0",
"@dynamic-labs/sdk-react-core": "^4.49.0",
"@dynamic-labs/solana": "^4.49.0",
"@dynamic-labs/solana-core": "^4.49.0",
"@dynamic-labs/wagmi-connector": "^4.49.0",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.4",
Expand All @@ -23,11 +29,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.7",
"@reown/appkit": "^1.8.2",
"@reown/appkit-adapter-solana": "^1.8.2",
"@reown/appkit-adapter-wagmi": "^1.8.2",
"@sentry/react": "^10.27.0",
"@solana/wallet-adapter-phantom": "^0.9.24",
"@solana/web3.js": "^1.95.8",
"@tanstack/react-query": "^5.75.5",
"@wagmi/core": "^2.17.2",
Expand Down
76 changes: 36 additions & 40 deletions apps/agentic-chat/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { createAppKit } from '@reown/appkit/react'
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum'
import { DynamicContextProvider, mergeNetworks } from '@dynamic-labs/sdk-react-core'
import { SolanaWalletConnectors } from '@dynamic-labs/solana'
import { DynamicWagmiConnector } from '@dynamic-labs/wagmi-connector'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Routes, Route, Navigate } from 'react-router-dom'
import { Toaster } from 'sonner'
import { WagmiProvider } from 'wagmi'

import { useAutoNetworkSwitch } from '@/hooks/useAutoNetworkSwitch'
import { useWalletAnalytics } from '@/hooks/useWalletAnalytics'
import { networks } from '@/lib/appkit'
import { solanaAdapter } from '@/lib/solana-config'
import { wagmiConfig, wagmiAdapter } from '@/lib/wagmi-config'
import { DYNAMIC_EVM_NETWORKS } from '@/lib/chains'
import { wagmiConfig } from '@/lib/wagmi-config'

import { Dashboard } from './dashboard/page'

Expand All @@ -23,29 +24,8 @@ const queryClient = new QueryClient({
},
})

const metadata = {
name: 'Agentic Chat',
description: 'ShapeShift Agentic Chat',
url: typeof window !== 'undefined' ? window.location.origin : 'https://chat.shapeshift.com',
icons: ['https://app.shapeshift.com/icon-512x512.png'],
}

// Initialize AppKit
if (import.meta.env.VITE_PROJECT_ID) {
createAppKit({
adapters: [wagmiAdapter, solanaAdapter],
projectId: import.meta.env.VITE_PROJECT_ID,
networks,
metadata,
features: {
swaps: false,
},
})
}

function AppContent() {
useWalletAnalytics()
useAutoNetworkSwitch()

return (
<Routes>
Expand All @@ -58,20 +38,36 @@ function AppContent() {

function App() {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<AppContent />
<Toaster
theme="dark"
closeButton
toastOptions={{
classNames: {
closeButton: '!right-0 !left-auto !translate-x-[50%] !-translate-y-[25%]',
},
}}
/>
</QueryClientProvider>
</WagmiProvider>
<DynamicContextProvider
theme="dark"
settings={{
environmentId: import.meta.env.VITE_DYNAMIC_ENVIRONMENT_ID,
walletConnectors: [EthereumWalletConnectors, SolanaWalletConnectors],
initialAuthenticationMode: 'connect-only',
overrides: {
evmNetworks: dashboardNetworks => {
return mergeNetworks(DYNAMIC_EVM_NETWORKS, dashboardNetworks)
},
},
}}
>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<DynamicWagmiConnector>
<AppContent />
<Toaster
theme="dark"
closeButton
toastOptions={{
classNames: {
closeButton: '!right-0 !left-auto !translate-x-[50%] !-translate-y-[25%]',
},
}}
/>
</DynamicWagmiConnector>
</QueryClientProvider>
</WagmiProvider>
</DynamicContextProvider>
)
}

Expand Down
55 changes: 31 additions & 24 deletions apps/agentic-chat/src/components/CustomConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useAppKit, useAppKitAccount, useAppKitNetwork, useWalletInfo } from '@reown/appkit/react'
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'
import { isSolanaWallet } from '@dynamic-labs/solana'
import { NETWORK_ICONS } from '@shapeshiftoss/utils'
import { useCallback, useMemo } from 'react'
import { useChainId } from 'wagmi'

import { useWalletConnection } from '@/hooks/useWalletConnection'
import { SOLANA_CAIP_ID } from '@/lib/chains'
import { truncateAddress } from '@/lib/utils'

import { Button } from './ui/Button'
Expand All @@ -11,46 +14,50 @@ type CustomConnectButtonProps = {
}

export const CustomConnectButton = ({ onConnectedClick }: CustomConnectButtonProps) => {
const { open } = useAppKit()
const { address } = useAppKitAccount()
const { isConnected } = useWalletConnection()
const { caipNetwork } = useAppKitNetwork()
const { walletInfo } = useWalletInfo()

const handleConnect = () => {
void open()
}
const { setShowAuthFlow, primaryWallet } = useDynamicContext()
const chainId = useChainId()

const handleConnect = useCallback(() => {
setShowAuthFlow(true)
}, [setShowAuthFlow])

const handleOpenAccount = () => {
const handleOpenAccount = useCallback(() => {
if (onConnectedClick) {
onConnectedClick()
} else {
void open({ view: 'Account' })
}
}
}, [onConnectedClick])

const address = primaryWallet?.address
const truncatedAddress = useMemo(() => (address ? truncateAddress(address) : ''), [address])

const walletIcon = primaryWallet?.connector?.metadata?.icon
const walletName = useMemo(() => primaryWallet?.connector?.name, [primaryWallet?.connector?.name])

const isPrimarySolana = useMemo(() => primaryWallet && isSolanaWallet(primaryWallet), [primaryWallet])

if (!isConnected) {
const caipChainId = useMemo(
() => (isPrimarySolana ? SOLANA_CAIP_ID : chainId ? `eip155:${chainId}` : undefined),
[isPrimarySolana, chainId]
)

const networkIcon = useMemo(() => (caipChainId ? NETWORK_ICONS[caipChainId] : undefined), [caipChainId])

if (!primaryWallet) {
return (
<Button onClick={handleConnect} variant="default">
Connect Wallet
</Button>
)
}

const caipChainId = caipNetwork?.caipNetworkId
const networkIcon = caipChainId ? NETWORK_ICONS[caipChainId] : undefined
const truncatedAddress = address ? truncateAddress(address) : ''

return (
<Button onClick={handleOpenAccount} variant="wallet" className="gap-2">
<div className="relative w-6 h-6">
{walletInfo?.icon && (
<img src={walletInfo.icon} alt={walletInfo.name || 'Wallet'} className="w-6 h-6 rounded-full" />
)}
{walletIcon && <img src={walletIcon} alt={walletName || 'Wallet'} className="w-6 h-6 rounded-full" />}
{networkIcon && (
<img
src={networkIcon}
alt={caipNetwork?.name || 'Network'}
alt="Network"
className="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 rounded-full border border-gray-800"
/>
)}
Expand Down
71 changes: 33 additions & 38 deletions apps/agentic-chat/src/components/Portfolio/NetworkWalletRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,51 @@ import { X } from 'lucide-react'

import { truncateAddress } from '@/lib/utils'

import { Button } from '../ui/Button'
import { CopyButton } from '../ui/CopyButton'
import { DropdownMenuLabel } from '../ui/DropdownMenu'
import { IconButton } from '../ui/IconButton'

type NetworkWalletRowProps = {
label: string
address: string | undefined
icon: string | undefined
isConnected: boolean
isActive?: boolean
onConnect: () => void
onDisconnect: () => void
}

export function NetworkWalletRow({
label,
address,
icon,
isConnected,
onConnect,
onDisconnect,
}: NetworkWalletRowProps) {
export function NetworkWalletRow({ label, address, icon, isActive, onConnect, onDisconnect }: NetworkWalletRowProps) {
return (
<>
<DropdownMenuLabel className="text-xs text-muted-foreground pb-0">{label}</DropdownMenuLabel>
{isConnected ? (
<div className="px-2 py-1 flex items-center justify-between group">
<div className="flex items-center gap-2 min-w-0">
{icon && <img src={icon} alt={`${label} Wallet`} className="w-4 h-4 shrink-0 rounded-full" />}
<span className="text-sm font-mono truncate">{address ? truncateAddress(address) : ''}</span>
</div>
<div className="flex items-center">
{address && <CopyButton value={address} className="text-muted-foreground hover:text-foreground" />}
<IconButton
icon={<X className="w-4 h-4" />}
label={`Disconnect ${label}`}
size="sm"
variant="ghost"
onClick={onDisconnect}
className="text-muted-foreground hover:text-destructive"
/>
</div>
<div
className="px-2 py-1 flex items-center justify-between group cursor-pointer hover:bg-accent/50 rounded-sm transition-colors"
onClick={onConnect}
>
<div className="flex items-center gap-2 min-w-0">
{icon && <img src={icon} alt={`${label} Wallet`} className="w-4 h-4 shrink-0 rounded-full" />}
<div className="flex items-center gap-2">
<span className="text-sm font-mono truncate">{address ? truncateAddress(address) : ''}</span>
{isActive && (
<span className="text-[10px] font-medium bg-primary/10 text-primary px-1.5 py-0.5 rounded">Active</span>
)}
</div>
) : (
<div className="px-2 py-1.5">
<Button onClick={onConnect} variant="outline" size="sm" className="w-full">
Connect {label}
</Button>
</div>
)}
</>
</div>
<div className="flex items-center">
{address && (
<div onClick={e => e.stopPropagation()} onKeyDown={e => e.stopPropagation()} role="button" tabIndex={0}>
<CopyButton value={address} className="text-muted-foreground hover:text-foreground" />
</div>
)}
<IconButton
icon={<X className="w-4 h-4" />}
label={`Disconnect ${label}`}
size="sm"
variant="ghost"
onClick={e => {
e.stopPropagation()
onDisconnect()
}}
className="text-muted-foreground hover:text-destructive"
/>
</div>
</div>
)
}
Loading
Loading