From bb579ba208ff8de7c8ac2ae84aef67bc1679888c Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 3 Nov 2025 08:25:44 -0800 Subject: [PATCH 01/10] Add mainnet forking support --- cadence/contracts/FCL.cdc | 5 + cadence/scripts/getAccounts.cdc | 2 +- cadence/transactions/addAccount.cdc | 8 + cadence/transactions/fundFLOW.cdc | 4 +- cadence/transactions/newAccount.cdc | 6 +- cadence/transactions/updateAccount.cdc | 2 +- components/AccountsList.tsx | 25 +- components/AnyAccountForm.tsx | 193 ++++ components/AuthzDetailsTable.tsx | 7 +- components/AuthzHeader.tsx | 28 +- contexts/AuthzContext.tsx | 31 +- contexts/ConfigContext.tsx | 47 +- flow.json | 162 ++-- go/wallet/bundle.zip | Bin 1365363 -> 1368738 bytes imports/1654653399040a61/FlowToken.cdc | 297 +++++++ imports/1d7e57aa55817448/MetadataViews.cdc | 821 ++++++++++++++++++ imports/1d7e57aa55817448/NonFungibleToken.cdc | 293 +++++++ imports/1d7e57aa55817448/ViewResolver.cdc | 59 ++ imports/f233dcee88fe0abe/Burner.cdc | 50 ++ imports/f233dcee88fe0abe/FungibleToken.cdc | 332 +++++++ .../FungibleTokenMetadataViews.cdc | 186 ++++ package-lock.json | 94 +- pages/fcl/authn.tsx | 11 + src/accounts.ts | 39 + src/constants.ts | 26 + src/fclConfig.ts | 52 +- src/harness/cmds/m1.js | 2 +- src/harness/cmds/m2.js | 2 +- 28 files changed, 2626 insertions(+), 158 deletions(-) create mode 100644 cadence/transactions/addAccount.cdc create mode 100644 components/AnyAccountForm.tsx create mode 100644 imports/1654653399040a61/FlowToken.cdc create mode 100644 imports/1d7e57aa55817448/MetadataViews.cdc create mode 100644 imports/1d7e57aa55817448/NonFungibleToken.cdc create mode 100644 imports/1d7e57aa55817448/ViewResolver.cdc create mode 100644 imports/f233dcee88fe0abe/Burner.cdc create mode 100644 imports/f233dcee88fe0abe/FungibleToken.cdc create mode 100644 imports/f233dcee88fe0abe/FungibleTokenMetadataViews.cdc diff --git a/cadence/contracts/FCL.cdc b/cadence/contracts/FCL.cdc index a8fe8b2..570e083 100644 --- a/cadence/contracts/FCL.cdc +++ b/cadence/contracts/FCL.cdc @@ -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) diff --git a/cadence/scripts/getAccounts.cdc b/cadence/scripts/getAccounts.cdc index fdc3378..d205d87 100644 --- a/cadence/scripts/getAccounts.cdc +++ b/cadence/scripts/getAccounts.cdc @@ -1,4 +1,4 @@ -import "FCL" +import FCL from 0xFCL access(all) fun main(): &[FCL.FCLAccount] { return FCL.accounts().values diff --git a/cadence/transactions/addAccount.cdc b/cadence/transactions/addAccount.cdc new file mode 100644 index 0000000..4262367 --- /dev/null +++ b/cadence/transactions/addAccount.cdc @@ -0,0 +1,8 @@ +import FCL from 0xFCL + +transaction(address: Address, label: String, scopes: [String]) { + prepare(acct: &Account) { + FCL.add(address: address, label: label, scopes: scopes) + } +} + diff --git a/cadence/transactions/fundFLOW.cdc b/cadence/transactions/fundFLOW.cdc index c94bd71..2e49104 100644 --- a/cadence/transactions/fundFLOW.cdc +++ b/cadence/transactions/fundFLOW.cdc @@ -1,5 +1,5 @@ -import "FlowToken" -import "FungibleToken" +import FlowToken from 0xFlowToken +import FungibleToken from 0xFungibleToken transaction(address: Address, amount: UFix64) { let tokenAdmin: &FlowToken.Administrator diff --git a/cadence/transactions/newAccount.cdc b/cadence/transactions/newAccount.cdc index 064932e..4ac5ac4 100644 --- a/cadence/transactions/newAccount.cdc +++ b/cadence/transactions/newAccount.cdc @@ -1,6 +1,6 @@ -import "FCL" -import "FlowToken" -import "FungibleToken" +import FCL from 0xFCL +import FlowToken from 0xFlowToken +import FungibleToken from 0xFungibleToken /// This transaction creates a new account and funds it with /// an initial balance of FLOW tokens. diff --git a/cadence/transactions/updateAccount.cdc b/cadence/transactions/updateAccount.cdc index 27bb549..c15ad18 100644 --- a/cadence/transactions/updateAccount.cdc +++ b/cadence/transactions/updateAccount.cdc @@ -1,4 +1,4 @@ -import "FCL" +import FCL from 0xFCL transaction(address: Address, label: String, scopes: [String]) { prepare() { diff --git a/components/AccountsList.tsx b/components/AccountsList.tsx index 62d0311..20c3802 100644 --- a/components/AccountsList.tsx +++ b/components/AccountsList.tsx @@ -8,6 +8,7 @@ import accountGenerator from "src/accountGenerator" import {Box, Themed} from "theme-ui" import {SXStyles} from "types" import FormErrors from "./FormErrors" +import useConfig from "hooks/useConfig" const styles: SXStyles = { accountCreated: { @@ -19,9 +20,13 @@ const styles: SXStyles = { mb: 3, }, plusButtonContainer: { - height: 90, display: "flex", alignItems: "center", + gap: 2, + }, + plusButtonContainerColumn: { + flexDirection: "column", + alignItems: "stretch", }, footer: { lineHeight: 1.7, @@ -33,6 +38,7 @@ const styles: SXStyles = { export default function AccountsList({ accounts, onEditAccount, + onUseAnyAccount, createdAccountAddress, flowAccountAddress, flowAccountPrivateKey, @@ -40,12 +46,14 @@ export default function AccountsList({ }: { 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 (
@@ -78,7 +86,20 @@ export default function AccountsList({ /> ))} - + + {forkMode && ( + + Use Existing Address (Fork Mode) + + )} onEditAccount(accountGenerator(accounts.length - 1)) diff --git a/components/AnyAccountForm.tsx b/components/AnyAccountForm.tsx new file mode 100644 index 0000000..8004c99 --- /dev/null +++ b/components/AnyAccountForm.tsx @@ -0,0 +1,193 @@ +/** @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(null) + + return ( + + initialValues={{ + address: "", + label: "", + }} + validate={values => { + const errors: Record = {} + 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}) => ( + <> +
+
+ + + + + + + + + + + + + + + {error &&
{error}
} + {Object.values(formErrors).length > 0 && ( +
+ {Object.values(formErrors).join(". ")} +
+ )} +
+
+
+
+
+ + +
+
+
+ + )} + + ) +} diff --git a/components/AuthzDetailsTable.tsx b/components/AuthzDetailsTable.tsx index 63b3ebc..3345c01 100644 --- a/components/AuthzDetailsTable.tsx +++ b/components/AuthzDetailsTable.tsx @@ -60,9 +60,10 @@ const styles: SXStyles = { }, } -export function AuthzDetailsAccount({account}: {account: Account}) { +export function AuthzDetailsAccount({account}: {account: Account | null}) { const {currentUser} = useAuthzContext() - const isCurrent = account.address === currentUser.address + if (!account) return null + const isCurrent = currentUser && account.address === currentUser.address return (
- {account.label} + {account.label || account.address}
{account.address}
diff --git a/components/AuthzHeader.tsx b/components/AuthzHeader.tsx index 2a3bbfb..8c6a4c1 100644 --- a/components/AuthzHeader.tsx +++ b/components/AuthzHeader.tsx @@ -60,17 +60,23 @@ function AuthzHeader({
- -