Skip to content

Commit d736f55

Browse files
authored
Updates send flow: Sendtags & Send ID (#477)
* fix search result scrollview, unique keys * send amount visual updates * add active coin * add send recipient * lint clean ups, organize, fix no profile redirect * wip add id type, todo update profile lookup on send * show when no send account * send screen test * simple send screen test * send screen test: no send account * add README to app * clean up styles * can send starting from profile page specs * adds emit Received test * just follow gnosis * fix profile specs * fix profile and send spec * linting * sometimes firefox needs double tap
1 parent 9a45d73 commit d736f55

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2521
-641
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ tmp
6767
**/Dockerfile
6868
**/.dockerignore
6969
**/.tamagui
70+
.watchman**

Tiltfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ next_app_resource_deps = [
9090
"ui:generate-theme",
9191
"daimo-expo-passkeys:build",
9292
"anvil:fixtures",
93+
"shovel",
9394
] + ([
9495
"aa_bundler:base",
9596
] if not CI else [])
@@ -129,6 +130,7 @@ if CFG.dockerize:
129130
"supabase",
130131
"anvil:fixtures",
131132
"aa_bundler:base",
133+
"shovel",
132134
],
133135
)
134136
else:

apps/next/pages/send/confirm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SendConfirmScreen } from 'app/features/send/confirm/screen'
22
import Head from 'next/head'
33
import { userProtectedGetSSP } from 'utils/userProtected'
44
import type { NextPageWithLayout } from '../_app'
5-
import { SendLayout } from 'app/features/send/layout.web'
5+
import { HomeLayout } from 'app/features/home/layout.web'
66
import { TopNav } from 'app/components/TopNav'
77

88
export const Page: NextPageWithLayout = () => {
@@ -20,7 +20,7 @@ export const Page: NextPageWithLayout = () => {
2020
export const getServerSideProps = userProtectedGetSSP()
2121

2222
Page.getLayout = (children) => (
23-
<SendLayout TopNav={<TopNav header="Preview and Send" noSubroute />}>{children}</SendLayout>
23+
<HomeLayout TopNav={<TopNav header="Preview and Send" noSubroute />}>{children}</HomeLayout>
2424
)
2525

2626
export default Page

apps/next/pages/send/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { SendScreen } from 'app/features/send/screen'
22
import Head from 'next/head'
33
import { userProtectedGetSSP } from 'utils/userProtected'
44
import type { NextPageWithLayout } from '../_app'
5-
import { SendLayout } from 'app/features/send/layout.web'
65
import { SendTopNav } from 'app/features/send/components/SendTopNav'
6+
import { HomeLayout } from 'app/features/home/layout.web'
77

88
export const Page: NextPageWithLayout = () => {
99
return (
@@ -19,6 +19,6 @@ export const Page: NextPageWithLayout = () => {
1919

2020
export const getServerSideProps = userProtectedGetSSP()
2121

22-
Page.getLayout = (children) => <SendLayout TopNav={<SendTopNav />}>{children}</SendLayout>
22+
Page.getLayout = (children) => <HomeLayout TopNav={<SendTopNav />}>{children}</HomeLayout>
2323

2424
export default Page

packages/api/src/routers/sendAccount.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
baseMainnet,
33
baseMainnetClient,
4+
entryPointAddress,
45
sendAccountFactoryAbi,
56
sendAccountFactoryAddress,
67
sendTokenAbi,
@@ -14,7 +15,7 @@ import { hexToBytea } from 'app/utils/hexToBytea'
1415
import { COSEECDHAtoXY } from 'app/utils/passkeys'
1516
import { supabaseAdmin } from 'app/utils/supabase/admin'
1617
import { throwIf } from 'app/utils/throwIf'
17-
import { USEROP_SALT, entrypoint, getSendAccountCreateArgs } from 'app/utils/userop'
18+
import { USEROP_SALT, getSendAccountCreateArgs } from 'app/utils/userop'
1819
import debug from 'debug'
1920
import PQueue from 'p-queue'
2021
import { getSenderAddress } from 'permissionless'
@@ -91,6 +92,7 @@ export const sendAccountRouter = createTRPCRouter({
9192

9293
const xyPubKey = COSEECDHAtoXY(base16.decode(cosePublicKeyB16))
9394
const factory = sendAccountFactoryAddress[baseMainnetClient.chain.id]
95+
const entryPoint = entryPointAddress[baseMainnetClient.chain.id]
9496
const factoryData = encodeFunctionData({
9597
abi: [getAbiItem({ abi: sendAccountFactoryAbi, name: 'createAccount' })],
9698
args: getSendAccountCreateArgs(xyPubKey),
@@ -99,7 +101,7 @@ export const sendAccountRouter = createTRPCRouter({
99101
const senderAddress = await getSenderAddress(baseMainnetClient, {
100102
factory,
101103
factoryData,
102-
entryPoint: entrypoint.address,
104+
entryPoint,
103105
})
104106
const raw_credential_id = `\\x${rawCredentialIDB16}`
105107
const public_key = `\\x${cosePublicKeyB16}`

packages/app/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
# Send App
3+
4+
This is the `app` package, the main package of Send App. It contains the screens, components, and utilities. It is a react native app built with [Solito](https://solito.dev), [Tamagui](https://tamagui.dev), and [Expo](https://expo.dev).
5+
6+
Everything is organized into screens. Screens are composed of components and utilities. The screens are imported into platform specific applications found in the root `apps` folder. For now, there are only two apps, `expo` and `next`. The `expo` app is the native app built with [Expo](https://expo.dev) and the `next` app is a web app built with [Next.js](https://nextjs.org) and the pages router.
7+
8+
## Testing
9+
10+
Tests are written using [Jest](https://jestjs.io/docs/getting-started) and [React Native Testing Library](https://callstack.github.io/react-native-testing-library/docs/start/intro). The tests are meant to be run offline and without any network access. To do this, only the native platform is emulated. All platform specific APIs and backends are mocked.
11+
12+
You can run tests with `yarn test` or `yarn test --watch` to run the tests in watch mode.
13+
14+
### Mocks
15+
16+
The automatic mocks are found in `__mocks__` folders at the root of the `app` folder as a sibiling to the `package.json`. Files that match the import path of an existing node module or `app` package will be automatically mocked. For example, if you have a file at `packages/app/utils/useProfileLookup.ts`, an automatic mock is found at `packages/app/__mocks__/app/utils/useProfileLookup.ts`. These automatic mocks are mocked even without a call to `jest.mock` inside the test file.
17+
18+
#### Mocking ES6 Modules
19+
20+
The automatic mocks are powerful and make it easy to share mocks across multiple test files. However, it can be tricky to understand the mocking logic and where the mocks are coming from. When writing a new mock file, be sure to include a default export that also includes an `esModule` property set to `true`. This will ensure that the mock is treated as an ES6 module and will be automatically mocked and included in the test file properly.
21+
22+
```ts
23+
export default {
24+
__esModule: true,
25+
// mock logic
26+
}
27+
```

packages/app/__mocks__/@my/wagmi/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ const mockMyWagmi = {
3737
tokenPaymasterAddress: {
3838
845337: '0x5e421172B27658f2bD83BCBD13738ADdE00E7CA9',
3939
},
40+
entryPointAddress: {
41+
845337: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
42+
},
43+
sendVerifierAbi: [
44+
{
45+
type: 'function',
46+
inputs: [
47+
{ name: 'message', internalType: 'bytes', type: 'bytes' },
48+
{ name: 'signature', internalType: 'bytes', type: 'bytes' },
49+
{ name: 'x', internalType: 'uint256', type: 'uint256' },
50+
{ name: 'y', internalType: 'uint256', type: 'uint256' },
51+
],
52+
name: 'verifySignature',
53+
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
54+
stateMutability: 'view',
55+
},
56+
],
57+
sendVerifierProxyAddress: {
58+
845337: '0x6c38612d3f645711dd080711021fC1bA998a5628',
59+
},
4060
useWriteErc20Transfer: jest.fn().mockReturnValue({
4161
data: '0x123',
4262
writeContract: jest.fn(),
@@ -51,4 +71,7 @@ export const baseMainnet = mockMyWagmi.baseMainnet
5171
export const usdcAddress = mockMyWagmi.usdcAddress
5272
export const sendTokenAddress = mockMyWagmi.sendTokenAddress
5373
export const tokenPaymasterAddress = mockMyWagmi.tokenPaymasterAddress
74+
export const entryPointAddress = mockMyWagmi.entryPointAddress
75+
export const sendVerifierAbi = mockMyWagmi.sendVerifierAbi
76+
export const sendVerifierProxyAddress = mockMyWagmi.sendVerifierProxyAddress
5477
export default mockMyWagmi

packages/app/__mocks__/app/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
# Mocks for app
3+
4+
This directory contains mocks for the app package. By matching the import path, Jest will automatically mock the module and return the mocked value.
5+
6+
For example, if you have a module named `app/utils/useProfileLookup` and you want to mock it, you can create a file named `__mocks__/app/utils/useProfileLookup.ts` and export a function that returns a mocked value.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const mockUseProfileLookup = jest.fn(() => ({
2+
isLoading: false,
3+
error: null,
4+
data: null,
5+
}))
6+
7+
export const useProfileLookup = mockUseProfileLookup
8+
9+
export default {
10+
useProfileLookup,
11+
}
Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
export const createParam = jest.fn().mockReturnValue({
2-
useParam: jest.fn(),
3-
useParams: jest.fn(),
1+
export const createParam = jest.fn(() => {
2+
return {
3+
useParam: jest.fn(),
4+
useParams: jest.fn(),
5+
}
46
})
5-
export const useParam = jest.fn()
6-
export const useParams = jest.fn()
7-
87
export default {
98
createParam,
10-
useParam,
11-
useParams,
129
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const useRouter = jest.fn()
2+
3+
export default {
4+
useRouter,
5+
}

packages/app/components/FormFields/CoinField.tsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
import { ChevronDown, ChevronUp } from '@tamagui/lucide-icons'
2-
import { useTsController } from '@ts-react/form'
3-
import { useId, useState } from 'react'
41
import {
52
Adapt,
3+
Button,
4+
FieldError,
65
Fieldset,
6+
Paragraph,
77
Select,
8-
type SelectProps,
8+
Shake,
99
Sheet,
10+
Spinner,
1011
Theme,
12+
Tooltip,
1113
XStack,
1214
YStack,
1315
getFontSize,
1416
isWeb,
1517
useThemeName,
16-
Spinner,
17-
Paragraph,
18+
type SelectProps,
1819
type TooltipProps,
19-
Tooltip,
20-
Button,
21-
FieldError,
22-
Shake,
2320
} from '@my/ui'
24-
import { usdcAddress, baseMainnet } from '@my/wagmi'
25-
import { type coin, coins } from 'app/data/coins'
26-
import { useSendAccount } from 'app/utils/send-accounts'
21+
import { baseMainnet, usdcAddress } from '@my/wagmi'
22+
import { ChevronDown, ChevronUp, CheckCircle as IconCheckCircle } from '@tamagui/lucide-icons'
23+
import { useTsController } from '@ts-react/form'
2724
import { IconError, IconX } from 'app/components/icons'
25+
import { coins, type coin } from 'app/data/coins'
2826
import formatAmount from 'app/utils/formatAmount'
29-
import { type UseBalanceReturnType, useBalance } from 'wagmi'
27+
import { useSendAccount } from 'app/utils/send-accounts'
28+
import { useId, useState } from 'react'
29+
import { useBalance, type UseBalanceReturnType } from 'wagmi'
3030
import { IconCoin } from '../icons/IconCoin'
31-
3231
export const CoinField = ({ native = false, ...props }: Pick<SelectProps, 'size' | 'native'>) => {
3332
const [isOpen, setIsOpen] = useState(false)
3433

@@ -119,7 +118,14 @@ export const CoinField = ({ native = false, ...props }: Pick<SelectProps, 'size'
119118
<Select.Group disabled={disabled} space="$0">
120119
{/* <Select.Label>{label}</Select.Label> */}
121120
{coins.map((coin, i) => {
122-
return <CoinFieldItem coin={coin} index={i} key={coin.token} />
121+
return (
122+
<CoinFieldItem
123+
active={coin.token === field.value}
124+
coin={coin}
125+
index={i}
126+
key={coin.token}
127+
/>
128+
)
123129
})}
124130
</Select.Group>
125131
{/* special icon treatment for native */}
@@ -149,9 +155,11 @@ export const CoinField = ({ native = false, ...props }: Pick<SelectProps, 'size'
149155
}
150156

151157
const CoinFieldItem = ({
158+
active,
152159
coin,
153160
index,
154161
}: {
162+
active: boolean
155163
coin: coin
156164
index: number
157165
}) => {
@@ -176,6 +184,7 @@ const CoinFieldItem = ({
176184
>
177185
{coin.symbol}
178186
</Select.ItemText>
187+
{active && <IconCheckCircle color={'$color12'} size={'$1.5'} />}
179188
</XStack>
180189
<XStack gap={'$3.5'} ai={'center'}>
181190
<TokenBalance balance={balance} />

packages/app/components/FormFields/TextField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const TextField = (props: InputProps & { fieldsetProps?: FieldsetProps })
8484
</Tooltip.Content>
8585
<Tooltip.Trigger>
8686
<Input
87-
accessibilityLabel={label}
87+
accessibilityLabel={label ?? field.name}
8888
disabled={disabled}
8989
maxLength={maxLength}
9090
borderWidth={0}

0 commit comments

Comments
 (0)