From 9651e07156d5eebff7219839535ba6b755be4d80 Mon Sep 17 00:00:00 2001 From: Kant Date: Fri, 27 Sep 2024 16:19:14 +0200 Subject: [PATCH] feat: bootstrap wallet-api custom ACRE module and handlers [LIVE-13948] --- .changeset/flat-comics-know.md | 8 + CODEOWNERS | 1 + .../components/WebPTXPlayer/CustomHandlers.ts | 6 +- .../components/WebPTXPlayer/index.tsx | 5 +- .../WebPlatformPlayer/CustomHandlers.ts | 123 ++++++++ .../components/WebPlatformPlayer/index.tsx | 10 +- .../exchange/Swap2/Form/SwapWebView.tsx | 2 +- .../exchange/Swap2/Form/SwapWebViewDemo3.tsx | 2 +- .../components/WebPTXPlayer/CustomHandlers.ts | 6 +- .../src/components/WebPTXPlayer/index.tsx | 5 +- .../WebPlatformPlayer/CustomHandlers.ts | 99 ++++++ .../components/WebPlatformPlayer/index.tsx | 10 +- .../components/Web3Player/index.tsx | 10 +- libs/ledger-live-common/.unimportedrc.json | 1 + libs/ledger-live-common/package.json | 1 + .../src/wallet-api/ACRE/server.ts | 294 ++++++++++++++++++ .../src/wallet-api/ACRE/tracking.ts | 98 ++++++ libs/wallet-api-acre-module/.eslintignore | 2 + libs/wallet-api-acre-module/.eslintrc.js | 20 ++ .../wallet-api-acre-module/.unimportedrc.json | 5 + libs/wallet-api-acre-module/package.json | 42 +++ .../scripts/createModulePackage.mjs | 3 + libs/wallet-api-acre-module/src/index.ts | 104 +++++++ libs/wallet-api-acre-module/src/types.ts | 41 +++ libs/wallet-api-acre-module/tsconfig.json | 15 + pnpm-lock.yaml | 81 ++--- 26 files changed, 916 insertions(+), 78 deletions(-) create mode 100644 .changeset/flat-comics-know.md create mode 100644 apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/CustomHandlers.ts create mode 100644 apps/ledger-live-mobile/src/components/WebPlatformPlayer/CustomHandlers.ts create mode 100644 libs/ledger-live-common/src/wallet-api/ACRE/server.ts create mode 100644 libs/ledger-live-common/src/wallet-api/ACRE/tracking.ts create mode 100644 libs/wallet-api-acre-module/.eslintignore create mode 100644 libs/wallet-api-acre-module/.eslintrc.js create mode 100644 libs/wallet-api-acre-module/.unimportedrc.json create mode 100644 libs/wallet-api-acre-module/package.json create mode 100644 libs/wallet-api-acre-module/scripts/createModulePackage.mjs create mode 100644 libs/wallet-api-acre-module/src/index.ts create mode 100644 libs/wallet-api-acre-module/src/types.ts create mode 100644 libs/wallet-api-acre-module/tsconfig.json diff --git a/.changeset/flat-comics-know.md b/.changeset/flat-comics-know.md new file mode 100644 index 000000000000..516920b9b485 --- /dev/null +++ b/.changeset/flat-comics-know.md @@ -0,0 +1,8 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +"@ledgerhq/live-common": minor +"@ledgerhq/wallet-api-acre-module": minor +--- + +feat: bootstrap wallet-api custom ACRE module and handlers diff --git a/CODEOWNERS b/CODEOWNERS index 7cd24b2db479..edd3ed1398a5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -75,6 +75,7 @@ apps/ledger-live-mobile/src/screens/PTX/ @ledgerhq/p apps/ledger-live-mobile/src/screens/Swap/ @ledgerhq/ptx libs/ledger-live-common/src/exchange/ @ledgerhq/ptx libs/exchange-module/ @ledgerhq/ptx +libs/wallet-api-acre-module/ @ledgerhq/ptx libs/ledgerjs/packages/hw-app-exchange/ @ledgerhq/ptx # Wallet API team **/PlatformAppProviderWrapper.tsx @ledgerhq/wallet-api diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/CustomHandlers.ts b/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/CustomHandlers.ts index aff021103b69..9f04b1d0cfce 100644 --- a/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/CustomHandlers.ts +++ b/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/CustomHandlers.ts @@ -6,9 +6,8 @@ import { ExchangeType, } from "@ledgerhq/live-common/wallet-api/Exchange/server"; import trackingWrapper from "@ledgerhq/live-common/wallet-api/Exchange/tracking"; -import { Operation } from "@ledgerhq/types-live"; +import { AccountLike, Operation } from "@ledgerhq/types-live"; import { track } from "~/renderer/analytics/segment"; -import { flattenAccountsSelector } from "~/renderer/reducers/accounts"; import { currentRouteNameRef } from "~/renderer/analytics/screenRefs"; import { closePlatformAppDrawer, openExchangeDrawer } from "~/renderer/actions/UI"; import { WebviewProps } from "../Web3AppWebview/types"; @@ -16,9 +15,8 @@ import { context } from "~/renderer/drawers/Provider"; import WebviewErrorDrawer from "~/renderer/screens/exchange/Swap2/Form/WebviewErrorDrawer"; import { platformAppDrawerStateSelector } from "~/renderer/reducers/UI"; -export function usePTXCustomHandlers(manifest: WebviewProps["manifest"]) { +export function usePTXCustomHandlers(manifest: WebviewProps["manifest"], accounts: AccountLike[]) { const dispatch = useDispatch(); - const accounts = useSelector(flattenAccountsSelector); const { setDrawer } = React.useContext(context); const { isOpen: isDrawerOpen } = useSelector(platformAppDrawerStateSelector); diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/index.tsx b/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/index.tsx index 19dcbfae6e5d..3c73db4638a8 100644 --- a/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/WebPTXPlayer/index.tsx @@ -1,5 +1,7 @@ import React, { useRef, useState } from "react"; import styled from "styled-components"; +import { useSelector } from "react-redux"; +import { flattenAccountsSelector } from "~/renderer/reducers/accounts"; import { Web3AppWebview } from "../Web3AppWebview"; import { TopBar } from "./TopBar"; import Box from "../Box"; @@ -24,7 +26,8 @@ export default function WebPTXPlayer({ manifest, inputs }: WebviewProps) { const webviewAPIRef = useRef(null); const [webviewState, setWebviewState] = useState(initialWebviewState); - const customHandlers = usePTXCustomHandlers(manifest); + const accounts = useSelector(flattenAccountsSelector); + const customHandlers = usePTXCustomHandlers(manifest, accounts); return ( diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/CustomHandlers.ts b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/CustomHandlers.ts new file mode 100644 index 000000000000..c13c9c995c86 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/CustomHandlers.ts @@ -0,0 +1,123 @@ +import { useMemo } from "react"; +import { ipcRenderer } from "electron"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { AccountLike } from "@ledgerhq/types-live"; +import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; +import { addPendingOperation } from "@ledgerhq/live-common/account/index"; +import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; +import { handlers as acreHandlers } from "@ledgerhq/live-common/wallet-api/ACRE/server"; +import trackingWrapper from "@ledgerhq/live-common/wallet-api/ACRE/tracking"; +import { track } from "~/renderer/analytics/segment"; +import { openModal } from "~/renderer/actions/modals"; +import { setDrawer } from "~/renderer/drawers/Provider"; +import { OperationDetails } from "~/renderer/drawers/OperationDetails"; +import { currentRouteNameRef } from "~/renderer/analytics/screenRefs"; +import { updateAccountWithUpdater } from "~/renderer/actions/accounts"; +import { WebviewProps } from "../Web3AppWebview/types"; + +export function useACRECustomHandlers(manifest: WebviewProps["manifest"], accounts: AccountLike[]) { + const { pushToast } = useToasts(); + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const tracking = useMemo( + () => + trackingWrapper( + ( + eventName: string, + properties?: Record | null, + mandatory?: boolean | null, + ) => + track( + eventName, + { + ...properties, + flowInitiatedFrom: + currentRouteNameRef.current === "Platform Catalog" + ? "Discover" + : currentRouteNameRef.current, + }, + mandatory, + ), + ), + [], + ); + + return useMemo(() => { + return { + ...acreHandlers({ + accounts, + tracking, + manifest, + uiHooks: { + "custom.acre.messageSign": ({ account, message, onSuccess, onError, onCancel }) => { + ipcRenderer.send("show-app", {}); + dispatch( + openModal("MODAL_SIGN_MESSAGE", { + account, + message, + onConfirmationHandler: onSuccess, + onFailHandler: onError, + onClose: onCancel, + }), + ); + }, + "custom.acre.transactionSign": ({ + account, + parentAccount, + signFlowInfos: { canEditFees, hasFeesProvided, liveTx }, + options, + onSuccess, + onError, + }) => { + ipcRenderer.send("show-app", {}); + dispatch( + openModal("MODAL_SIGN_TRANSACTION", { + canEditFees, + stepId: canEditFees && !hasFeesProvided ? "amount" : "summary", + transactionData: liveTx, + useApp: options?.hwAppId, + dependencies: options?.dependencies, + account, + parentAccount, + onResult: onSuccess, + onCancel: onError, + manifestId: manifest.id, + manifestName: manifest.name, + }), + ); + }, + "custom.acre.transactionBroadcast": ( + account, + parentAccount, + mainAccount, + optimisticOperation, + ) => { + dispatch( + updateAccountWithUpdater(mainAccount.id, account => + addPendingOperation(account, optimisticOperation), + ), + ); + + pushToast({ + id: optimisticOperation.id, + type: "operation", + title: t("platform.flows.broadcast.toast.title"), + text: t("platform.flows.broadcast.toast.text"), + icon: "info", + callback: () => { + tracking.broadcastOperationDetailsClick(manifest); + setDrawer(OperationDetails, { + operationId: optimisticOperation.id, + accountId: account.id, + parentId: parentAccount?.id as string | undefined | null, + }); + }, + }); + }, + }, + }), + }; + }, [accounts, tracking, manifest, dispatch, pushToast, t]); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.tsx b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.tsx index 9e2166cd579d..5a7ea146df20 100644 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useRef, useState } from "react"; import styled from "styled-components"; +import { useSelector } from "react-redux"; import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; import { handlers as loggerHandlers } from "@ledgerhq/live-common/wallet-api/CustomLogger/server"; @@ -10,6 +11,8 @@ import { WebviewAPI, WebviewProps, WebviewState } from "../Web3AppWebview/types" import { initialWebviewState } from "../Web3AppWebview/helpers"; import { usePTXCustomHandlers } from "../WebPTXPlayer/CustomHandlers"; import { useCurrentAccountHistDB } from "~/renderer/screens/platform/v2/hooks"; +import { flattenAccountsSelector } from "~/renderer/reducers/accounts"; +import { useACRECustomHandlers } from "./CustomHandlers"; export const Container = styled.div` display: flex; @@ -37,14 +40,17 @@ export default function WebPlatformPlayer({ manifest, inputs, onClose, config, . const webviewAPIRef = useRef(null); const [webviewState, setWebviewState] = useState(initialWebviewState); - const customPTXHandlers = usePTXCustomHandlers(manifest); + const accounts = useSelector(flattenAccountsSelector); + const customACREHandlers = useACRECustomHandlers(manifest, accounts); + const customPTXHandlers = usePTXCustomHandlers(manifest, accounts); const customHandlers = useMemo(() => { return { ...loggerHandlers, + ...customACREHandlers, ...customPTXHandlers, }; - }, [customPTXHandlers]); + }, [customACREHandlers, customPTXHandlers]); const onStateChange: WebviewProps["onStateChange"] = state => { setWebviewState(state); diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx index f1b838d799a0..9c67c6d22655 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebView.tsx @@ -138,7 +138,7 @@ const SwapWebView = ({ const swapDefaultTrack = useGetSwapTrackingProperties(); const hasSwapState = !!swapState; - const customPTXHandlers = usePTXCustomHandlers(manifest); + const customPTXHandlers = usePTXCustomHandlers(manifest, accounts); const { fromCurrency, addressFrom, addressTo } = useMemo(() => { const [, , fromCurrency, addressFrom] = diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx index e56b37a63070..0de04f03ae7c 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/Swap2/Form/SwapWebViewDemo3.tsx @@ -118,7 +118,7 @@ const SwapWebView = ({ manifest, liveAppUnavailable }: SwapWebProps) => { const { networkStatus } = useNetworkStatus(); const isOffline = networkStatus === NetworkStatus.OFFLINE; - const customPTXHandlers = usePTXCustomHandlers(manifest); + const customPTXHandlers = usePTXCustomHandlers(manifest, accounts); const customHandlers = useMemo( () => ({ ...loggerHandlers, diff --git a/apps/ledger-live-mobile/src/components/WebPTXPlayer/CustomHandlers.ts b/apps/ledger-live-mobile/src/components/WebPTXPlayer/CustomHandlers.ts index d0e00f93d1de..58ba4509cee9 100644 --- a/apps/ledger-live-mobile/src/components/WebPTXPlayer/CustomHandlers.ts +++ b/apps/ledger-live-mobile/src/components/WebPTXPlayer/CustomHandlers.ts @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { useSelector } from "react-redux"; +import type { AccountLike } from "@ledgerhq/types-live"; import type { Device } from "@ledgerhq/live-common/hw/actions/types"; import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; import trackingWrapper from "@ledgerhq/live-common/wallet-api/Exchange/tracking"; @@ -8,7 +8,6 @@ import { ExchangeType, } from "@ledgerhq/live-common/wallet-api/Exchange/server"; import { useNavigation } from "@react-navigation/native"; -import { flattenAccountsSelector } from "~/reducers/accounts"; import { StackNavigatorNavigation } from "../RootNavigator/types/helpers"; import { BaseNavigatorStackParamList } from "../RootNavigator/types/BaseNavigator"; import { track } from "~/analytics"; @@ -16,10 +15,9 @@ import { NavigatorName, ScreenName } from "~/const"; import { currentRouteNameRef } from "~/analytics/screenRefs"; import { WebviewProps } from "../Web3AppWebview/types"; -export function usePTXCustomHandlers(manifest: WebviewProps["manifest"]) { +export function usePTXCustomHandlers(manifest: WebviewProps["manifest"], accounts: AccountLike[]) { const navigation = useNavigation>(); const [device, setDevice] = useState(); - const accounts = useSelector(flattenAccountsSelector); const tracking = useMemo( () => diff --git a/apps/ledger-live-mobile/src/components/WebPTXPlayer/index.tsx b/apps/ledger-live-mobile/src/components/WebPTXPlayer/index.tsx index 1411d81e656d..2e17d66426ab 100644 --- a/apps/ledger-live-mobile/src/components/WebPTXPlayer/index.tsx +++ b/apps/ledger-live-mobile/src/components/WebPTXPlayer/index.tsx @@ -8,6 +8,7 @@ import { TouchableOpacity, } from "react-native"; import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; import { Flex, Icon, Text } from "@ledgerhq/native-ui"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; @@ -22,6 +23,7 @@ import { useNavigation } from "@react-navigation/native"; import { useTheme } from "styled-components/native"; +import { flattenAccountsSelector } from "~/reducers/accounts"; import { WebviewAPI, WebviewState } from "../Web3AppWebview/types"; import { Web3AppWebview } from "../Web3AppWebview"; import { RootNavigationComposite, StackNavigatorNavigation } from "../RootNavigator/types/helpers"; @@ -262,7 +264,8 @@ export const WebPTXPlayer = ({ } }, [config, disableHeader, isInternalApp, manifest, navigation, onClose, webviewState?.url]); - const customHandlers = usePTXCustomHandlers(manifest); + const accounts = useSelector(flattenAccountsSelector); + const customHandlers = usePTXCustomHandlers(manifest, accounts); return ( diff --git a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/CustomHandlers.ts b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/CustomHandlers.ts new file mode 100644 index 000000000000..98ee473d3d9e --- /dev/null +++ b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/CustomHandlers.ts @@ -0,0 +1,99 @@ +import { useMemo } from "react"; +import { useNavigation } from "@react-navigation/native"; +import { AccountLike, SignedOperation } from "@ledgerhq/types-live"; +import type { Transaction } from "@ledgerhq/live-common/generated/types"; +import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; +import { handlers as acreHandlers } from "@ledgerhq/live-common/wallet-api/ACRE/server"; +import trackingWrapper from "@ledgerhq/live-common/wallet-api/ACRE/tracking"; +import { track } from "~/analytics"; +import { NavigatorName, ScreenName } from "~/const"; +import { currentRouteNameRef } from "~/analytics/screenRefs"; +import { StackNavigatorNavigation } from "../RootNavigator/types/helpers"; +import { BaseNavigatorStackParamList } from "../RootNavigator/types/BaseNavigator"; +import { WebviewProps } from "../Web3AppWebview/types"; +import prepareSignTransaction from "../Web3AppWebview/liveSDKLogic"; + +export function useACRECustomHandlers(manifest: WebviewProps["manifest"], accounts: AccountLike[]) { + const navigation = useNavigation>(); + + const tracking = useMemo( + () => + trackingWrapper((eventName: string, properties?: Record | null) => + track(eventName, { + ...properties, + flowInitiatedFrom: + currentRouteNameRef.current === "Platform Catalog" + ? "Discover" + : currentRouteNameRef.current, + }), + ), + [], + ); + + return useMemo(() => { + return { + ...acreHandlers({ + accounts, + tracking, + manifest, + uiHooks: { + "custom.acre.messageSign": ({ account, message, onSuccess, onError, onCancel }) => { + navigation.navigate(NavigatorName.SignMessage, { + screen: ScreenName.SignSummary, + params: { + message, + accountId: account.id, + onConfirmationHandler: onSuccess, + onFailHandler: onError, + }, + onClose: onCancel, + }); + }, + "custom.acre.transactionSign": ({ + account, + parentAccount, + signFlowInfos: { liveTx }, + options, + onSuccess, + onError, + }) => { + const tx = prepareSignTransaction(account, parentAccount, liveTx); + + navigation.navigate(NavigatorName.SignTransaction, { + screen: ScreenName.SignTransactionSummary, + params: { + currentNavigation: ScreenName.SignTransactionSummary, + nextNavigation: ScreenName.SignTransactionSelectDevice, + transaction: tx as Transaction, + accountId: account.id, + parentId: parentAccount ? parentAccount.id : undefined, + appName: options?.hwAppId, + dependencies: options?.dependencies, + onSuccess: ({ + signedOperation, + transactionSignError, + }: { + signedOperation: SignedOperation; + transactionSignError: Error; + }) => { + if (transactionSignError) { + onError(transactionSignError); + } else { + onSuccess(signedOperation); + + const n = + navigation.getParent< + StackNavigatorNavigation + >() || navigation; + n.pop(); + } + }, + onError, + }, + }); + }, + }, + }), + }; + }, [accounts, tracking, manifest, navigation]); +} diff --git a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/index.tsx b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/index.tsx index 4a2161e176ed..f9a0770a9488 100644 --- a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/index.tsx +++ b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/index.tsx @@ -1,6 +1,7 @@ import { LiveAppManifest } from "@ledgerhq/live-common/platform/types"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { StyleSheet, SafeAreaView, BackHandler, Platform } from "react-native"; +import { useSelector } from "react-redux"; import { useNavigation } from "@react-navigation/native"; import { Flex } from "@ledgerhq/native-ui"; @@ -19,6 +20,8 @@ import { InfoPanel } from "./InfoPanel"; import { usePTXCustomHandlers } from "../WebPTXPlayer/CustomHandlers"; import { WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; import { useCurrentAccountHistDB } from "~/screens/Platform/v2/hooks"; +import { flattenAccountsSelector } from "~/reducers/accounts"; +import { useACRECustomHandlers } from "./CustomHandlers"; type Props = { manifest: LiveAppManifest; @@ -78,14 +81,17 @@ const WebPlatformPlayer = ({ manifest, inputs }: Props) => { }); }, [manifest, navigation, webviewState]); - const customPTXHandlers = usePTXCustomHandlers(manifest); + const accounts = useSelector(flattenAccountsSelector); + const customACREHandlers = useACRECustomHandlers(manifest, accounts); + const customPTXHandlers = usePTXCustomHandlers(manifest, accounts); const customHandlers = useMemo(() => { return { ...loggerHandlers, + ...customACREHandlers, ...customPTXHandlers, }; - }, [customPTXHandlers]); + }, [customACREHandlers, customPTXHandlers]); return ( diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/index.tsx index b920dcfdf4e6..c954364aa446 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubApp/components/Web3Player/index.tsx @@ -1,13 +1,16 @@ import React, { ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { StyleSheet, View, BackHandler, Platform } from "react-native"; import { SharedValue } from "react-native-reanimated"; +import { useSelector } from "react-redux"; import { CurrentAccountHistDB, safeGetRefValue } from "@ledgerhq/live-common/wallet-api/react"; import { handlers as loggerHandlers } from "@ledgerhq/live-common/wallet-api/CustomLogger/server"; import { AppManifest, WalletAPICustomHandlers } from "@ledgerhq/live-common/wallet-api/types"; import { WebviewAPI, WebviewState } from "~/components/Web3AppWebview/types"; import { Web3AppWebview } from "~/components/Web3AppWebview"; +import { useACRECustomHandlers } from "~/components/WebPlatformPlayer/CustomHandlers"; import { usePTXCustomHandlers } from "~/components/WebPTXPlayer/CustomHandlers"; import { useCurrentAccountHistDB } from "~/screens/Platform/v2/hooks"; +import { flattenAccountsSelector } from "~/reducers/accounts"; import { BottomBar } from "./BottomBar"; import { InfoPanel } from "./InfoPanel"; @@ -55,14 +58,17 @@ const WebPlatformPlayer = ({ } }, [handleHardwareBackPress]); - const customPTXHandlers = usePTXCustomHandlers(manifest); + const accounts = useSelector(flattenAccountsSelector); + const customACREHandlers = useACRECustomHandlers(manifest, accounts); + const customPTXHandlers = usePTXCustomHandlers(manifest, accounts); const customHandlers = useMemo(() => { return { ...loggerHandlers, + ...customACREHandlers, ...customPTXHandlers, }; - }, [customPTXHandlers]); + }, [customACREHandlers, customPTXHandlers]); return ( diff --git a/libs/ledger-live-common/.unimportedrc.json b/libs/ledger-live-common/.unimportedrc.json index 93928437fda7..f00d4630d4f1 100644 --- a/libs/ledger-live-common/.unimportedrc.json +++ b/libs/ledger-live-common/.unimportedrc.json @@ -266,6 +266,7 @@ "src/utils/runOnceWhen.ts", "src/user.ts", "src/walletConnect/index.ts", + "src/wallet-api/ACRE/server.ts", "src/wallet-api/Exchange/server.ts", "src/wallet-api/CustomLogger/client.ts", "src/wallet-api/constants.ts", diff --git a/libs/ledger-live-common/package.json b/libs/ledger-live-common/package.json index fb35cf7d9dbb..c26ac6060797 100644 --- a/libs/ledger-live-common/package.json +++ b/libs/ledger-live-common/package.json @@ -180,6 +180,7 @@ "@ledgerhq/live-wallet": "workspace:^", "@ledgerhq/logs": "workspace:^", "@ledgerhq/speculos-transport": "workspace:^", + "@ledgerhq/wallet-api-acre-module": "workspace:^", "@ledgerhq/wallet-api-client": "^1.5.10", "@ledgerhq/wallet-api-core": "^1.11.0", "@ledgerhq/wallet-api-exchange-module": "workspace:^", diff --git a/libs/ledger-live-common/src/wallet-api/ACRE/server.ts b/libs/ledger-live-common/src/wallet-api/ACRE/server.ts new file mode 100644 index 000000000000..339386ae2722 --- /dev/null +++ b/libs/ledger-live-common/src/wallet-api/ACRE/server.ts @@ -0,0 +1,294 @@ +/* eslint-disable no-console */ +import { RPCHandler, WalletHandlers, customWrapper } from "@ledgerhq/wallet-api-server"; +import { deserializeTransaction } from "@ledgerhq/wallet-api-core"; +import { + getParentAccount, + getMainAccount, + makeEmptyTokenAccount, + isTokenAccount, + isAccount, +} from "@ledgerhq/coin-framework/account/index"; +import { Account, AccountLike, AnyMessage, Operation, SignedOperation } from "@ledgerhq/types-live"; +import { findTokenById } from "@ledgerhq/cryptoassets"; +import { + MessageSignParams, + MessageSignResult, + TransactionSignAndBroadcastParams, + TransactionSignAndBroadcastResult, + TransactionSignParams, + TransactionSignResult, +} from "@ledgerhq/wallet-api-acre-module"; +import { TrackingAPI } from "./tracking"; +import { AppManifest } from "../types"; +import { + getAccountIdFromWalletAccountId, + getWalletAPITransactionSignFlowInfos, +} from "../converters"; +import { getAccountBridge } from "../../bridge"; +import { Transaction } from "../../generated/types"; +import { UserRefusedOnDevice } from "@ledgerhq/errors"; +import { getEnv } from "@ledgerhq/live-env"; +import { prepareMessageToSign } from "../../hw/signMessage"; + +type Handlers = { + "custom.acre.messageSign": RPCHandler; + "custom.acre.transactionSign": RPCHandler; + "custom.acre.transactionSignAndBroadcast": RPCHandler< + TransactionSignAndBroadcastResult, + TransactionSignAndBroadcastParams + >; +}; + +type ACREUiHooks = { + "custom.acre.messageSign": (params: { + account: AccountLike; + message: AnyMessage; + onSuccess: (signature: string) => void; + onError: (error: Error) => void; + onCancel: () => void; + }) => void; + "custom.acre.transactionSign": (params: { + account: AccountLike; + parentAccount: Account | undefined; + signFlowInfos: { + canEditFees: boolean; + hasFeesProvided: boolean; + liveTx: Partial; + }; + options: Parameters[0]["options"]; + onSuccess: (signedOperation: SignedOperation) => void; + onError: (error: Error) => void; + }) => void; + "custom.acre.transactionBroadcast"?: ( + account: AccountLike, + parentAccount: Account | undefined, + mainAccount: Account, + optimisticOperation: Operation, + ) => void; +}; + +export const handlers = ({ + accounts, + tracking, + manifest, + uiHooks: { + "custom.acre.messageSign": uiMessageSign, + "custom.acre.transactionSign": uiTransactionSign, + "custom.acre.transactionBroadcast": uiTransactionBroadcast, + }, +}: { + accounts: AccountLike[]; + tracking: TrackingAPI; + manifest: AppManifest; + uiHooks: ACREUiHooks; +}) => { + function signTransaction({ + accountId: walletAccountId, + rawTransaction, + options, + tokenCurrency, + }: TransactionSignParams) { + const transaction = deserializeTransaction(rawTransaction); + + tracking.signTransactionRequested(manifest); + + if (!transaction) { + tracking.signTransactionFail(manifest); + return Promise.reject(new Error("Transaction required")); + } + + const accountId = getAccountIdFromWalletAccountId(walletAccountId); + if (!accountId) { + tracking.signTransactionFail(manifest); + return Promise.reject(new Error(`accountId ${walletAccountId} unknown`)); + } + + const account = accounts.find(account => account.id === accountId); + + if (!account) { + tracking.signTransactionFail(manifest); + return Promise.reject(new Error("Account required")); + } + + const parentAccount = getParentAccount(account, accounts); + + const accountFamily = isTokenAccount(account) + ? parentAccount?.currency.family + : account.currency.family; + + const mainAccount = getMainAccount(account, parentAccount); + const currency = tokenCurrency ? findTokenById(tokenCurrency) : null; + const signerAccount = currency ? makeEmptyTokenAccount(mainAccount, currency) : account; + + const { canEditFees, liveTx, hasFeesProvided } = getWalletAPITransactionSignFlowInfos({ + walletApiTransaction: transaction, + account: mainAccount, + }); + + if (accountFamily !== liveTx.family) { + return Promise.reject( + new Error( + `Account and transaction must be from the same family. Account family: ${accountFamily}, Transaction family: ${liveTx.family}`, + ), + ); + } + + const signFlowInfos = { + canEditFees, + liveTx, + hasFeesProvided, + }; + + return new Promise((resolve, reject) => + uiTransactionSign({ + account: signerAccount, + parentAccount, + signFlowInfos, + options, + onSuccess: signedOperation => { + tracking.signTransactionSuccess(manifest); + resolve(signedOperation); + }, + onError: error => { + tracking.signTransactionFail(manifest); + reject(error); + }, + }), + ); + } + + return { + "custom.acre.messageSign": customWrapper(async params => { + if (!params) { + tracking.signMessageNoParams(manifest); + // Maybe return an error instead + return { hexSignedMessage: "" }; + } + + tracking.signMessageRequested(manifest); + + const { accountId: walletAccountId, hexMessage } = params; + + const accountId = getAccountIdFromWalletAccountId(walletAccountId); + if (!accountId) { + tracking.signMessageFail(manifest); + return Promise.reject(new Error(`accountId ${walletAccountId} unknown`)); + } + + const account = accounts.find(account => account.id === accountId); + if (account === undefined) { + tracking.signMessageFail(manifest); + return Promise.reject(new Error("account not found")); + } + + let formattedMessage: AnyMessage; + try { + if (isAccount(account)) { + formattedMessage = prepareMessageToSign(account, hexMessage); + } else { + throw new Error("account provided should be the main one"); + } + } catch (error) { + tracking.signMessageFail(manifest); + return Promise.reject(error); + } + + return new Promise((resolve, reject) => { + return uiMessageSign({ + account, + message: formattedMessage, + onSuccess: signature => { + tracking.signMessageSuccess(manifest); + resolve({ + hexSignedMessage: signature.replace("0x", ""), + }); + }, + onCancel: () => { + tracking.signMessageFail(manifest); + reject(new UserRefusedOnDevice()); + }, + onError: error => { + tracking.signMessageFail(manifest); + reject(error); + }, + }); + }); + }), + "custom.acre.transactionSign": customWrapper( + async params => { + if (!params) { + tracking.signTransactionNoParams(manifest); + // Maybe return an error instead + return { signedTransactionHex: "" }; + } + + const signedOperation = await signTransaction(params); + + return { + signedTransactionHex: Buffer.from(signedOperation.signature).toString("hex"), + }; + }, + ), + "custom.acre.transactionSignAndBroadcast": customWrapper< + TransactionSignAndBroadcastParams, + TransactionSignAndBroadcastResult + >(async params => { + if (!params) { + tracking.signTransactionAndBroadcastNoParams(manifest); + // Maybe return an error instead + return { transactionHash: "" }; + } + + const signedOperation = await signTransaction(params); + + if (!signedOperation) { + tracking.broadcastFail(manifest); + return Promise.reject(new Error("Transaction required")); + } + + const { accountId: walletAccountId, tokenCurrency } = params; + + const accountId = getAccountIdFromWalletAccountId(walletAccountId); + if (!accountId) { + tracking.broadcastFail(manifest); + return Promise.reject(new Error(`accountId ${walletAccountId} unknown`)); + } + + const account = accounts.find(account => account.id === accountId); + if (!account) { + tracking.broadcastFail(manifest); + return Promise.reject(new Error("Account required")); + } + + const currency = tokenCurrency ? findTokenById(tokenCurrency) : null; + const parentAccount = getParentAccount(account, accounts); + const mainAccount = getMainAccount(account, parentAccount); + const signerAccount = currency ? makeEmptyTokenAccount(mainAccount, currency) : account; + + const bridge = getAccountBridge(signerAccount, parentAccount); + const broadcastAccount = getMainAccount(signerAccount, parentAccount); + + let optimisticOperation: Operation = signedOperation.operation; + + if (!getEnv("DISABLE_TRANSACTION_BROADCAST")) { + try { + optimisticOperation = await bridge.broadcast({ + account: broadcastAccount, + signedOperation, + }); + tracking.broadcastSuccess(manifest); + } catch (error) { + tracking.broadcastFail(manifest); + throw error; + } + } + + uiTransactionBroadcast && + uiTransactionBroadcast(account, parentAccount, mainAccount, optimisticOperation); + + return { + transactionHash: optimisticOperation.hash, + }; + }), + } as const satisfies Handlers; +}; diff --git a/libs/ledger-live-common/src/wallet-api/ACRE/tracking.ts b/libs/ledger-live-common/src/wallet-api/ACRE/tracking.ts new file mode 100644 index 000000000000..137e23cf19c7 --- /dev/null +++ b/libs/ledger-live-common/src/wallet-api/ACRE/tracking.ts @@ -0,0 +1,98 @@ +import type { AppManifest } from "../types"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Properties = Record | null; + +/** + * This signature is to be compatible with track method of `segment.js` file in LLM and LLD + * `track(event: string, properties: ?Object, mandatory: ?boolean)` in jsflow + * {@link @ledger-desktop/renderer/analytics/segment#track} + */ +type TrackExchange = (event: string, properties: Properties, mandatory: boolean | null) => void; + +/** + * Obtain Event data from WalletAPI App manifest + * + * @param {AppManifest} manifest + * @returns Object - event data + */ +function getEventData(manifest: AppManifest) { + return { walletAPI: manifest.name }; +} + +/** + * Wrap call to underlying trackCall function. + * @param trackCall + * @returns a dictionary of event to trigger. + */ +// Disabling explicit module boundary types as we're using const +// in order to get the exact type matching the tracking wrapper API +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export default function trackingWrapper(trackCall: TrackExchange) { + const track = (event: string, properties: Properties) => trackCall(event, properties, null); + + return { + // Sign message no params + signMessageNoParams: (manifest: AppManifest) => { + track("WalletAPI ACRE SignMessage no params", getEventData(manifest)); + }, + + signMessageRequested: (manifest: AppManifest) => { + track("WalletAPI ACRE sign message requested", getEventData(manifest)); + }, + + signMessageSuccess: (manifest: AppManifest) => { + track("WalletAPI ACRE sign message success", getEventData(manifest)); + }, + + signMessageFail: (manifest: AppManifest) => { + track("WalletAPI ACRE sign message fail", getEventData(manifest)); + }, + + signMessageUserRefused: (manifest: AppManifest) => { + track("WalletAPI ACRE sign message user refused", getEventData(manifest)); + }, + + // Sign transaction no params + signTransactionNoParams: (manifest: AppManifest) => { + track("WalletAPI ACRE SignTransaction no params", getEventData(manifest)); + }, + + // Sign transaction modal open + signTransactionRequested: (manifest: AppManifest) => { + track("WalletAPI ACRE SignTransaction", getEventData(manifest)); + }, + + // Failed to sign transaction (cancel or error) + signTransactionFail: (manifest: AppManifest) => { + track("WalletAPI ACRE SignTransaction Fail", getEventData(manifest)); + }, + + // Successfully signed transaction + signTransactionSuccess: (manifest: AppManifest) => { + track("WalletAPI ACRE SignTransaction Success", getEventData(manifest)); + }, + + // Sign transaction and broadcast no params + signTransactionAndBroadcastNoParams: (manifest: AppManifest) => { + track("WalletAPI ACRE SignTransactionAndBroadcast no params", getEventData(manifest)); + }, + + // Failed to broadcast a signed transaction + broadcastFail: (manifest: AppManifest) => { + track("WalletAPI ACRE Broadcast Fail", getEventData(manifest)); + }, + + // Successfully broadcast a signed transaction + broadcastSuccess: (manifest: AppManifest) => { + track("WalletAPI ACRE Broadcast Success", getEventData(manifest)); + }, + + // Successfully broadcast a signed transaction + broadcastOperationDetailsClick: (manifest: AppManifest) => { + track("WalletAPI ACRE Broadcast OpD Clicked", getEventData(manifest)); + }, + } as const; +} + +export type TrackingAPI = ReturnType; diff --git a/libs/wallet-api-acre-module/.eslintignore b/libs/wallet-api-acre-module/.eslintignore new file mode 100644 index 000000000000..d1edc58b3af1 --- /dev/null +++ b/libs/wallet-api-acre-module/.eslintignore @@ -0,0 +1,2 @@ +lib/ +lib-es/ \ No newline at end of file diff --git a/libs/wallet-api-acre-module/.eslintrc.js b/libs/wallet-api-acre-module/.eslintrc.js new file mode 100644 index 000000000000..8f6848a860b9 --- /dev/null +++ b/libs/wallet-api-acre-module/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + env: { + browser: true, + es6: true, + }, + overrides: [ + { + files: ["src/**/*.test.{ts,tsx}"], + env: { + "jest/globals": true, + }, + plugins: ["jest"], + }, + ], + rules: { + "no-console": ["error", { allow: ["warn", "error"] }], + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-explicit-any": "warn", + }, +}; diff --git a/libs/wallet-api-acre-module/.unimportedrc.json b/libs/wallet-api-acre-module/.unimportedrc.json new file mode 100644 index 000000000000..cbe49f21aaf0 --- /dev/null +++ b/libs/wallet-api-acre-module/.unimportedrc.json @@ -0,0 +1,5 @@ +{ + "entry": ["src/index.ts"], + "ignoreUnimported": [], + "ignoreUnresolved": [] +} diff --git a/libs/wallet-api-acre-module/package.json b/libs/wallet-api-acre-module/package.json new file mode 100644 index 000000000000..12332cee5471 --- /dev/null +++ b/libs/wallet-api-acre-module/package.json @@ -0,0 +1,42 @@ +{ + "name": "@ledgerhq/wallet-api-acre-module", + "version": "0.0.0", + "description": "Wallet-API ACRE Module for Ledger Live", + "license": "MIT", + "keywords": [ + "Ledger" + ], + "repository": { + "type": "git", + "url": "https://github.com/LedgerHQ/ledger-live.git" + }, + "bugs": { + "url": "https://github.com/LedgerHQ/ledger-live/issues" + }, + "homepage": "https://github.com/LedgerHQ/ledger-live/tree/develop/libs/wallet-api-acre-module", + "main": "lib/index.js", + "module": "lib-es/index.js", + "types": "lib/index.d.ts", + "files": [ + "/lib", + "/lib-es" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "clean": "rimraf lib lib-es", + "build": "tsc && tsc -m ES6 --outDir lib-es && node scripts/createModulePackage.mjs", + "prewatch": "pnpm build", + "watch": "tsc --watch", + "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx", + "lint:fix": "pnpm lint --fix" + }, + "dependencies": { + "@ledgerhq/wallet-api-client": "^1.5.10", + "@ledgerhq/wallet-api-core": "^1.11.0" + }, + "devDependencies": { + "@types/node": "^20.2.5" + } +} diff --git a/libs/wallet-api-acre-module/scripts/createModulePackage.mjs b/libs/wallet-api-acre-module/scripts/createModulePackage.mjs new file mode 100644 index 000000000000..feddd64c87bb --- /dev/null +++ b/libs/wallet-api-acre-module/scripts/createModulePackage.mjs @@ -0,0 +1,3 @@ +import { writeFileSync } from "fs"; + +writeFileSync("lib-es/package.json", '{ "type": "module" }'); diff --git a/libs/wallet-api-acre-module/src/index.ts b/libs/wallet-api-acre-module/src/index.ts new file mode 100644 index 000000000000..fde94e4a4341 --- /dev/null +++ b/libs/wallet-api-acre-module/src/index.ts @@ -0,0 +1,104 @@ +import { + CustomModule, + serializeTransaction, + Transaction, + TransactionSign, + TransactionSignAndBroadcast, +} from "@ledgerhq/wallet-api-client"; +import { + MessageSignParams, + MessageSignResult, + TransactionSignAndBroadcastParams, + TransactionSignAndBroadcastResult, + TransactionSignParams, + TransactionSignResult, +} from "./types"; + +export * from "./types"; + +// TODO maybe find a better way to type the available custom requests with correct types +export class AcreModule extends CustomModule { + /** + * Let the user sign the provided message. + * @param accountId - Ledger Live id of the account + * @param message - Message the user should sign + * @param address - Address to sign the message with + * + * @returns Message signed + * @throws {@link RpcError} if an error occured on server side + */ + async messageSign( + accountId: string, + message: Buffer, + address: string, + meta?: Record, + ) { + const result = await this.request( + "custom.acre.messageSign", + { + accountId, + hexMessage: message.toString("hex"), + address, + meta, + }, + ); + + return Buffer.from(result.hexSignedMessage, "hex"); + } + + /** + * Let the user sign a transaction that won't be broadcasted by the connected wallet + * @param accountId - id of the account + * @param transaction - The transaction object in the currency family-specific format + * @param options - Extra parameters + * + * @returns The raw signed transaction + * @throws {@link RpcError} if an error occured on server side + */ + async transactionSign( + accountId: string, + transaction: Transaction, + options?: TransactionSign["params"]["options"], + meta?: Record, + ): Promise { + const result = await this.request( + "custom.acre.transactionSign", + { + accountId, + rawTransaction: serializeTransaction(transaction), + options, + meta, + }, + ); + + return Buffer.from(result.signedTransactionHex, "hex"); + } + + /** + * Let the user sign and broadcast a transaction + * @param accountId - id of the account + * @param transaction - The transaction object in the currency family-specific format + * @param options - Extra parameters + * + * @returns The transaction hash + * @throws {@link RpcError} if an error occured on server side + */ + async transactionSignAndBroadcast( + accountId: string, + transaction: Transaction, + options?: TransactionSignAndBroadcast["params"]["options"], + meta?: Record, + ): Promise { + const result = await this.request< + TransactionSignAndBroadcastParams, + TransactionSignAndBroadcastResult + >("custom.acre.transactionSignAndBroadcast", { + accountId, + rawTransaction: serializeTransaction(transaction), + options, + meta, + }); + + return result.transactionHash; + } +} diff --git a/libs/wallet-api-acre-module/src/types.ts b/libs/wallet-api-acre-module/src/types.ts new file mode 100644 index 000000000000..5b9d7a640739 --- /dev/null +++ b/libs/wallet-api-acre-module/src/types.ts @@ -0,0 +1,41 @@ +import { RawTransaction } from "@ledgerhq/wallet-api-core"; + +export type MessageSignParams = { + accountId: string; + hexMessage: string; + address: string; + meta?: Record; +}; + +export type MessageSignResult = { + hexSignedMessage: string; +}; + +export type TransactionOptions = { + hwAppId?: string; + dependencies?: string[]; +}; + +export type TransactionSignParams = { + accountId: string; + rawTransaction: RawTransaction; + options?: TransactionOptions; + meta?: Record; + tokenCurrency?: string; +}; + +export type TransactionSignResult = { + signedTransactionHex: string; +}; + +export type TransactionSignAndBroadcastParams = { + accountId: string; + rawTransaction: RawTransaction; + options?: TransactionOptions; + meta?: Record; + tokenCurrency?: string; +}; + +export type TransactionSignAndBroadcastResult = { + transactionHash: string; +}; diff --git a/libs/wallet-api-acre-module/tsconfig.json b/libs/wallet-api-acre-module/tsconfig.json new file mode 100644 index 000000000000..3d4a6af532b1 --- /dev/null +++ b/libs/wallet-api-acre-module/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noImplicitAny": false, + "noImplicitThis": false, + "downlevelIteration": true, + "module": "commonjs", + "lib": ["es2020", "dom"], + "jsx": "react", + "outDir": "lib" + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b502993c8961..dc2679d6ab0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3578,6 +3578,9 @@ importers: '@ledgerhq/speculos-transport': specifier: workspace:^ version: link:../speculos-transport + '@ledgerhq/wallet-api-acre-module': + specifier: workspace:^ + version: link:../wallet-api-acre-module '@ledgerhq/wallet-api-client': specifier: ^1.5.10 version: 1.5.10 @@ -6908,6 +6911,19 @@ importers: specifier: ^0.2.9 version: 0.2.9 + libs/wallet-api-acre-module: + dependencies: + '@ledgerhq/wallet-api-client': + specifier: ^1.5.10 + version: 1.5.10 + '@ledgerhq/wallet-api-core': + specifier: ^1.11.0 + version: 1.11.0 + devDependencies: + '@types/node': + specifier: ^20.2.5 + version: 20.12.12 + tests/dummy-live-app: dependencies: '@ledgerhq/live-app-sdk': @@ -50042,8 +50058,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -50100,23 +50116,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): - dependencies: - debug: 4.3.4 - enhanced-resolve: 5.16.0 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.5 - is-core-module: 2.13.1 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 @@ -50161,17 +50160,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -50261,33 +50249,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 @@ -51104,12 +51065,12 @@ snapshots: react: 18.2.0 react-native: 0.73.6(@babel/core@7.24.3)(react@18.2.0) - expo-font@11.4.0(67hrxnryf4ycl5nhvln5laaykq): + expo-font@11.4.0(hxmq25pkdf6fvmayb5kzqynb3q): dependencies: expo: 49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) fontfaceobserver: 2.3.0 optionalDependencies: - expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) + expo-asset: 8.10.1(expo-constants@14.5.1(expo-modules-core@1.5.11)(expo@49.0.23)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-modules-core: 1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) react: 18.2.0 @@ -51234,7 +51195,7 @@ snapshots: expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-file-system: 15.4.5(tw2cl75ufjodqrrf2cewclqsqi) - expo-font: 11.4.0(67hrxnryf4ycl5nhvln5laaykq) + expo-font: 11.4.0(hxmq25pkdf6fvmayb5kzqynb3q) expo-keep-awake: 12.3.0(tw2cl75ufjodqrrf2cewclqsqi) expo-modules-autolinking: 1.5.1(55fu4l7dolnnxrys6pt2pnhfne) fbemitter: 3.0.0