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
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ Rivet is still the best dev tool that works well in the development flow of use

## Download

- **Chromium (Chrome, Brave, Arc, etc)** [TBD]
- **Chromium (Chrome, Brave, Arc, etc)** [[Download]](https://chromewebstore.google.com/detail/emajibkjkkilgdahffjhapcljjhhdpeb?utm_source=item-share-cb)
- **Firefox**: coming soon
- **Safari**: coming soon

### Nightly Release

DW: DevWallet is currently in active development. If you would like to try out the latest features, you can download the latest nightly build below:
DevWallet is currently in active development. If you would like to try out the latest features, you can download the latest nightly build below:

- **Chromium (Chrome, Brave, Arc, etc)**: [Download](https://github.com/D01-DayOne/dev-wallet/releases/latest)

Expand Down Expand Up @@ -147,11 +147,19 @@ bun run dev

This will run a script that will build the Web Extension, start a dev server for the Test Dapp, and automatically open Chrome with a fresh profile and the extension installed.

## Known Issues
## Multi-Wallet Support

DW: DevWallet uses the `window.ethereum` interface, which means it has some known conflicts with other wallets which also rely on `window.ethereum`. Once Dapps start to integrate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) to handle multiple injected wallets, this should not be a problem anymore.
DW: DevWallet supports both legacy `window.ethereum` injection and modern [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) multi-wallet discovery.

For best results it is recommended to run DW: DevWallet in its own Chrome profile, without any other conflicting browser wallets installed.
**EIP-6963 Support:**
- DevWallet announces itself via EIP-6963, allowing dapps to discover it alongside other wallets
- If another wallet (MetaMask, Coinbase, etc.) is already installed, DevWallet will NOT overwrite `window.ethereum` to avoid conflicts
- Dapps that support EIP-6963 can present users with a wallet selector to choose between DevWallet and other installed wallets
- The test dapp (`bun run dapp`) demonstrates EIP-6963 wallet discovery

**Legacy Dapp Compatibility:**
- If no other wallet is present, DevWallet will inject itself as `window.ethereum` for compatibility with older dapps
- For testing legacy dapps, run DevWallet in its own Chrome profile without other wallets installed

Helpful note: A fresh Chrome profile gets instantiated when running the dev script: `bun run dev`.

Expand Down
27 changes: 8 additions & 19 deletions src/components/Container.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import type { ReactNode } from 'react'
import { useNavigate } from 'react-router'
import {
Box,
Button,
Inline,
Inset,
Row,
Rows,
Separator,
Text,
} from '~/design-system'
import { Box, Button, Inset, Row, Rows, Separator, Text } from '~/design-system'
import type { RowProps } from '~/design-system/components/Rows'

export function Container({
Expand Down Expand Up @@ -48,28 +39,26 @@ export function Container({
display="flex"
paddingHorizontal="8px"
width="full"
style={{ minHeight: '44px' }}
style={{ minHeight: '44px', gap: '12px' }}
>
<Inline
alignVertical="center"
alignHorizontal="justify"
wrap={false}
>
<Box style={{ flex: 1, minWidth: 0 }}>
{typeof header === 'string' ? (
<Text size="16px">{header}</Text>
) : (
header
)}
{dismissable && (
</Box>
{dismissable && (
<Box style={{ flexShrink: 0 }}>
<Button.Symbol
label="Close"
height="24px"
onClick={() => navigate(-1)}
symbol="xmark"
variant="ghost primary"
/>
)}
</Inline>
</Box>
)}
</Box>
</Row>
<Row height="content">
Expand Down
45 changes: 42 additions & 3 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { type ReactNode, useLayoutEffect, useMemo, useState } from 'react'
import {
type ReactNode,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Link } from 'react-router-dom'
import { type Hex, formatGwei } from 'viem'

Expand Down Expand Up @@ -133,7 +140,33 @@ function Account() {
const [key, setKey] = useState(0)
useLayoutEffect(() => {
requestAnimationFrame(() => setKey((key) => key + 1))
}, [])
}, [account?.address])

// Flash animation when account changes
const [isFlashing, setIsFlashing] = useState(false)
const [transitionDuration, setTransitionDuration] = useState('200ms')
const prevAccountRef = useRef<string | undefined>(account?.address)

useEffect(() => {
if (
account?.address &&
account.address !== prevAccountRef.current &&
prevAccountRef.current !== undefined
) {
// Fast switch to flash color (50ms)
setTransitionDuration('50ms')
setIsFlashing(true)

// Then slowly fade back (200ms)
const fadeTimer = setTimeout(() => {
setTransitionDuration('200ms')
setIsFlashing(false)
}, 50)

return () => clearTimeout(fadeTimer)
}
prevAccountRef.current = account?.address
}, [account?.address])

if (!account) return null
return (
Expand All @@ -145,7 +178,13 @@ function Account() {
}}
display="flex"
height="full"
style={{ cursor: 'default' }}
style={{
cursor: 'default',
transition: `background-color ${transitionDuration} ease-out`,
...(isFlashing && {
backgroundColor: 'var(--flash-color)',
}),
}}
>
<Inset horizontal="8px">
{account && (
Expand Down
40 changes: 21 additions & 19 deletions src/components/tabs/TabsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Tabs_ from '@radix-ui/react-tabs'

import { Bleed, Box, Inline, Separator, Text } from '~/design-system'
import { Bleed, Box, Column, Columns, Separator, Text } from '~/design-system'

import * as styles from './TabsList.css'

Expand All @@ -16,27 +16,29 @@ export function TabsList({ items, onSelect }: TabsListProps) {
<>
<Tabs_.List asChild>
<div>
<Inline gap="8px">
<Columns gap="0px" width="full">
{items.map((item) => (
<Tabs_.Trigger
asChild
className={styles.tabTrigger}
key={item.value}
value={item.value}
>
<Box
alignItems="center"
justifyContent="center"
cursor="pointer"
display="flex"
onClick={() => onSelect?.(item)}
style={{ height: '32px' }}
<Column key={item.value}>
<Tabs_.Trigger
asChild
className={styles.tabTrigger}
value={item.value}
>
<Text size="11px">{item.label}</Text>
</Box>
</Tabs_.Trigger>
<Box
alignItems="center"
justifyContent="center"
cursor="pointer"
display="flex"
onClick={() => onSelect?.(item)}
style={{ height: '32px' }}
width="full"
>
<Text size="11px">{item.label}</Text>
</Box>
</Tabs_.Trigger>
</Column>
))}
</Inline>
</Columns>
</div>
</Tabs_.List>
<Bleed horizontal="-8px">
Expand Down
2 changes: 1 addition & 1 deletion src/design-system/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const ButtonRoot = forwardRef<HTMLDivElement, ButtonRootProps>(
onClick={onClick as any}
disabled={disabled}
className={[buttonHeightStyles[height], className]}
cursor={disabled ? 'not-allowed' : undefined}
cursor={disabled ? 'not-allowed' : 'pointer'}
display="flex"
alignItems="center"
justifyContent="center"
Expand Down
46 changes: 46 additions & 0 deletions src/design-system/components/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Box } from './Box'

type ToggleProps = {
checked: boolean
onChange: (checked: boolean) => void
disabled?: boolean
}

export function Toggle({ checked, onChange, disabled }: ToggleProps) {
return (
<Box
as="button"
type="button"
onClick={() => !disabled && onChange(!checked)}
disabled={disabled}
cursor={disabled ? 'not-allowed' : 'pointer'}
opacity={disabled ? '0.5' : undefined}
style={{
position: 'relative',
width: '37px',
height: '20px',
borderRadius: '4px',
backgroundColor: checked
? 'rgb(var(--toggle-active-bg))'
: 'rgb(var(--toggle-inactive-bg))',
border: 'none',
transition: 'background-color 200ms ease',
outline: 'none',
}}
>
<Box
style={{
position: 'absolute',
top: '2px',
left: checked ? '19px' : '2px',
width: '16px',
height: '16px',
borderRadius: '2px',
backgroundColor: 'white',
transition: 'left 200ms ease',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
}}
/>
</Box>
)
}
1 change: 1 addition & 0 deletions src/design-system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { Separator } from './components/Separator'
export { SFSymbol } from './components/SFSymbol'
export { Stack } from './components/Stack'
export { Text } from './components/Text'
export { Toggle } from './components/Toggle'

export { initializeTheme } from './utils/initializeTheme'
export { getTheme, setTheme } from './utils/theme'
6 changes: 6 additions & 0 deletions src/design-system/styles/theme.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ globalStyle(
[inheritedColorVars.accent]: toRgb(
defaultInheritedColor.accent.light,
),
'--flash-color': '#2a2520',
'--toggle-active-bg': '74, 144, 226',
'--toggle-inactive-bg': '232, 229, 224',
...assignVars(
backgroundColorVars,
mapValues(backgroundColor, (color) => toRgb(color.light.value)),
Expand Down Expand Up @@ -174,6 +177,9 @@ globalStyle(
colorScheme: 'dark',
vars: {
[inheritedColorVars.accent]: toRgb(defaultInheritedColor.accent.dark),
'--flash-color': '#f9f7f4',
'--toggle-active-bg': '57, 255, 20',
'--toggle-inactive-bg': '68, 71, 75',
...assignVars(
backgroundColorVars,
mapValues(backgroundColor, (color) => toRgb(color.dark.value)),
Expand Down
Loading