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
7 changes: 7 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
.next/
out/
build/
dist/
next-env.d.ts

1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"plugins": ["react", "prettier", "react-hooks"],
"rules": {
"react/react-in-jsx-scope": "off",
"react/no-unknown-property": ["error", { "ignore": ["sx"] }],
"no-console": "warn"
},
"settings": {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Build Bundle
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '20'
cache: 'npm'

- run: npm install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

strategy:
matrix:
node-version: [16.x]
node-version: [20.x]

steps:
- uses: actions/checkout@v3
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ yarn-error.log*
.vercel

.idea
/.env
/.env

# private keys
*.pkey

# TypeScript incremental build cache
tsconfig.tsbuildinfo
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ app to interact with the dev-wallet during development:

Navigate to http://localhost:8701/harness

### Fork Mode

When the dev wallet detects it's connected to a mainnet or testnet access node (instead of the local emulator), it automatically enters "fork mode". In fork mode:

- Transaction signature validation is assumed to be disabled on the network
- Users can authenticate with any existing account address on the network
- This is useful for testing against forked mainnet/testnet environments where signatures are not validated

Learn more about [forking mainnet/testnet with the Flow Emulator](https://developers.flow.com/build/tools/emulator).
🚀

## Contributing
Expand Down
5 changes: 5 additions & 0 deletions cadence/contracts/FCL.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ access(all) contract FCL {
return acct
}

access(all) fun add(address: Address, label: String, scopes: [String]) {
let acct = FCLAccount(address: address, label: label, scopes: scopes)
self.account.storage.borrow<&Root>(from: self.storagePath)!.add(acct)
}

access(all) fun update(address: Address, label: String, scopes: [String]) {
self.account.storage.borrow<&Root>(from: self.storagePath)!
.update(address: address, label: label, scopes: scopes)
Expand Down
8 changes: 8 additions & 0 deletions cadence/transactions/addAccount.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "FCL"

transaction(address: Address, label: String, scopes: [String]) {
prepare(acct: &Account) {
FCL.add(address: address, label: label, scopes: scopes)
}
}

3 changes: 2 additions & 1 deletion components/AccountBalances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {fundAccount} from "src/accounts"
import {formattedBalance} from "src/balance"
import {FLOW_TYPE, TokenTypes} from "src/constants"
import useConfig from "hooks/useConfig"
import {Label, Themed} from "theme-ui"
import {Label} from "theme-ui"
import {Themed} from "@theme-ui/mdx"
import {SXStyles} from "types"
import AccountSectionHeading from "./AccountSectionHeading"
import Button from "./Button"
Expand Down
8 changes: 7 additions & 1 deletion components/AccountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@

try {
if (account.address) {
await updateAccount(config, account.address, label!, scopesList)

Check warning on line 64 in components/AccountForm.tsx

View workflow job for this annotation

GitHub Actions / Static checks (20.x)

Forbidden non-null assertion

setSubmitting(false)
onSubmitComplete(undefined)
} else {
const address = await newAccount(config, label!, scopesList)

Check warning on line 69 in components/AccountForm.tsx

View workflow job for this annotation

GitHub Actions / Static checks (20.x)

Forbidden non-null assertion

setSubmitting(false)
onSubmitComplete(address)
Expand All @@ -79,7 +79,13 @@
}
}}
>
{({values, setFieldValue}) => (
{({
values,
setFieldValue,
}: {
values: {label: string | undefined; scopes: Set<string>}
setFieldValue: (field: string, value: any) => void

Check warning on line 87 in components/AccountForm.tsx

View workflow job for this annotation

GitHub Actions / Static checks (20.x)

Unexpected any. Specify a different type
}) => (
<>
<div sx={dialogStyles.body}>
<Form data-test="fund-account-form" sx={styles.form}>
Expand Down
3 changes: 2 additions & 1 deletion components/AccountListItemScopes.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/** @jsxImportSource theme-ui */
import Switch from "components/Switch"
import useAuthnContext from "hooks/useAuthnContext"
import {Label, Themed} from "theme-ui"
import {Label} from "theme-ui"
import {Themed} from "@theme-ui/mdx"
import {SXStyles} from "types"
import AccountSectionHeading from "./AccountSectionHeading"

Expand Down
31 changes: 28 additions & 3 deletions components/AccountsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import PlusButton from "components/PlusButton"
import useAuthnContext from "hooks/useAuthnContext"
import {Account, NewAccount} from "src/accounts"
import accountGenerator from "src/accountGenerator"
import {Box, Themed} from "theme-ui"
import {Box} from "theme-ui"
import {Themed} from "@theme-ui/mdx"
import {SXStyles} from "types"
import FormErrors from "./FormErrors"
import useConfig from "hooks/useConfig"

const styles: SXStyles = {
accountCreated: {
Expand All @@ -19,9 +21,15 @@ const styles: SXStyles = {
mb: 3,
},
plusButtonContainer: {
height: 90,
display: "flex",
alignItems: "center",
gap: 3,
},
plusButtonContainerColumn: {
flexDirection: "column",
alignItems: "stretch",
gap: 2,
mt: 3,
},
footer: {
lineHeight: 1.7,
Expand All @@ -33,19 +41,22 @@ const styles: SXStyles = {
export default function AccountsList({
accounts,
onEditAccount,
onUseAnyAccount,
createdAccountAddress,
flowAccountAddress,
flowAccountPrivateKey,
avatarUrl,
}: {
accounts: Account[]
onEditAccount: (account: Account | NewAccount) => void
onUseAnyAccount: () => void
createdAccountAddress: string | null
flowAccountAddress: string
flowAccountPrivateKey: string
avatarUrl: string
}) {
const {initError} = useAuthnContext()
const {forkMode} = useConfig()

return (
<div>
Expand Down Expand Up @@ -78,7 +89,21 @@ export default function AccountsList({
/>
))}
</Box>
<Box sx={styles.plusButtonContainer}>
<Box
sx={{
...styles.plusButtonContainer,
...(forkMode ? styles.plusButtonContainerColumn : {}),
}}
>
{forkMode && (
<PlusButton
onClick={onUseAnyAccount}
data-test="use-any-account-button"
icon="arrow"
>
Use Existing Address
</PlusButton>
)}
<PlusButton
onClick={() =>
onEditAccount(accountGenerator(accounts.length - 1))
Expand Down
3 changes: 2 additions & 1 deletion components/AccountsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {Account, NewAccount} from "src/accounts"
import {useEffect, useState} from "react"
import {chooseAccount} from "src/accountAuth"
import {formattedBalance} from "src/balance"
import {Flex, Themed} from "theme-ui"
import {Flex} from "theme-ui"
import {Themed} from "@theme-ui/mdx"
import {SXStyles} from "types"
import {getBaseUrl} from "src/utils"

Expand Down
191 changes: 191 additions & 0 deletions components/AnyAccountForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/** @jsxImportSource theme-ui */
import ConnectedAppHeader from "components/ConnectedAppHeader"
import {styles as dialogStyles} from "components/Dialog"
import {Field, Form, Formik} from "formik"
import {useState} from "react"
import {Account, addExistingAccount} from "src/accounts"
import {getBaseUrl} from "src/utils"
import {Box} from "theme-ui"
import {SXStyles} from "types"
import useAuthnContext from "hooks/useAuthnContext"
import useConfig from "hooks/useConfig"
import Button from "./Button"
import {CustomInputComponent} from "./Inputs"
import {chooseAccount} from "src/accountAuth"

const styles: SXStyles = {
form: {
position: "relative",
},
backButton: {
background: "none",
border: 0,
position: "absolute",
top: 0,
left: 0,
cursor: "pointer",
p: 0,
zIndex: 10,
},
actionsContainer: {
display: "flex",
alignItems: "center",
width: "100%",
borderTop: "1px solid",
borderColor: "gray.200",
backgroundColor: "white",
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
px: [10, 20],
},
actions: {
display: "flex",
flex: 1,
pt: 20,
pb: 20,
},
}

type FormValues = {
address: string
label: string
}

export default function AnyAccountForm({
onCancel,
flowAccountAddress,
flowAccountPrivateKey,
avatarUrl,
}: {
onCancel: () => void
flowAccountAddress: string
flowAccountPrivateKey: string
avatarUrl: string
}) {
const baseUrl = getBaseUrl()
const config = useConfig()
const {connectedAppConfig, appScopes} = useAuthnContext()
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)

return (
<Formik<FormValues>
initialValues={{
address: "",
label: "",
}}
validate={values => {
const errors: Record<string, string> = {}
if (!values.address) errors.address = "Address is required"
return errors
}}
onSubmit={async values => {
setError(null)
setSubmitting(true)
try {
// First, add the account to FCL storage
await addExistingAccount(
config,
values.address,
values.label || values.address,
appScopes as [string]
)

const account: Account = {
type: "ACCOUNT",
address: values.address,
keyId: 0,
label: values.label || values.address,
scopes: appScopes,
}

// Then authenticate with it
await chooseAccount(
baseUrl,
flowAccountPrivateKey,
account,
new Set(appScopes),
connectedAppConfig
)
} catch (e: unknown) {
setError(String(e))
setSubmitting(false)
}
}}
>
{({submitForm, errors: formErrors}) => (
<>
<div sx={dialogStyles.body}>
<Form sx={styles.form}>
<Button onClick={onCancel} sx={styles.backButton}>
<img src="/back-arrow.svg" />
</Button>

<Box mb={4}>
<ConnectedAppHeader
info={false}
title={"Use Existing Address"}
description={"Authenticate as any address on the network."}
flowAccountAddress={flowAccountAddress}
avatarUrl={avatarUrl}
/>
</Box>

<Box mb={4}>
<Field
component={CustomInputComponent}
inputLabel="Address"
name="address"
placeholder="0x..."
required
/>
</Box>

<Box mb={5}>
<Field
component={CustomInputComponent}
inputLabel="Label (optional)"
name="label"
placeholder="External account label"
/>
</Box>

{error && <div sx={{color: "red", fontSize: 1}}>{error}</div>}
{Object.values(formErrors).length > 0 && (
<div sx={{color: "red", fontSize: 1}}>
{Object.values(formErrors).join(". ")}
</div>
)}
</Form>
</div>
<div sx={dialogStyles.footer}>
<div sx={styles.actionsContainer}>
<div sx={styles.actions}>
<Button
onClick={onCancel}
type="button"
variant="ghost"
block
size="lg"
sx={{flex: 1, mr: 10, w: "50%"}}
>
Cancel
</Button>
<Button
type="button"
block
size="lg"
sx={{flex: 1, ml: 10, w: "50%"}}
disabled={submitting || Object.values(formErrors).length > 0}
onClick={submitForm}
>
Authenticate
</Button>
</div>
</div>
</div>
</>
)}
</Formik>
)
}
Loading
Loading