Skip to content

Commit

Permalink
feat: add draft send tez signing
Browse files Browse the repository at this point in the history
  • Loading branch information
OKendigelyan committed Feb 4, 2025
1 parent 7d5a662 commit a72756c
Show file tree
Hide file tree
Showing 23 changed files with 1,369 additions and 67 deletions.
4 changes: 4 additions & 0 deletions apps/mobile/app/(auth)/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useDataPolling } from "@umami/data-polling";

import { Home as HomeScreen } from "../../screens/Home";

export default function Home() {
useDataPolling();

return <HomeScreen />;
}
31 changes: 17 additions & 14 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { TamaguiProvider } from "tamagui";

import { PersistorLoader } from "../components/PersistorLoader";
import { AuthProvider, ReactQueryProvider } from "../providers";
import store, { persistor } from "../store/store";
import { ModalProvider } from "../providers/ModalProvider";
import { persistor, store } from "../store";
import { tamaguiConfig } from "../tamagui.config";

export default function RootLayout() {
Expand All @@ -19,18 +20,20 @@ export default function RootLayout() {
}, []);

return (
<ToastProvider toast={{} as Toast}>
<ReactQueryProvider>
<TamaguiProvider config={tamaguiConfig} defaultTheme={colorScheme!}>
<Provider store={store}>
<PersistGate loading={<PersistorLoader />} persistor={persistor}>
<AuthProvider>
<Slot />
</AuthProvider>
</PersistGate>
</Provider>
</TamaguiProvider>
</ReactQueryProvider>
</ToastProvider>
<Provider store={store}>
<PersistGate loading={<PersistorLoader />} persistor={persistor}>
<ToastProvider toast={{} as Toast}>
<ReactQueryProvider>
<TamaguiProvider config={tamaguiConfig} defaultTheme={colorScheme!}>
<ModalProvider>
<AuthProvider>
<Slot />
</AuthProvider>
</ModalProvider>
</TamaguiProvider>
</ReactQueryProvider>
</ToastProvider>
</PersistGate>
</Provider>
);
}
21 changes: 21 additions & 0 deletions apps/mobile/components/ModalCloseButton/ModalCloseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { X } from "@tamagui/lucide-icons";
import { Button, styled } from "tamagui";

import { useModal } from "../../providers/ModalProvider";

export const ModalCloseButton = () => {
const { hideModal } = useModal();

return <CloseButton icon={<X />} onPress={hideModal} />;
};

const CloseButton = styled(Button, {
position: "absolute",
top: 0,
right: 0,
zIndex: 1000,
borderRadius: 100,
width: "auto",
height: "auto",
padding: 10,
});
1 change: 1 addition & 0 deletions apps/mobile/components/ModalCloseButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ModalCloseButton";
156 changes: 156 additions & 0 deletions apps/mobile/components/SendFlow/SignButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { type TezosToolkit } from "@taquito/taquito";
import type { BatchWalletOperation } from "@taquito/taquito/dist/types/wallet/batch-operation";
import {
type ImplicitAccount,
type LedgerAccount,
type MnemonicAccount,
type SecretKeyAccount,
type SocialAccount,
} from "@umami/core";
import { useAsyncActionHandler, useGetSecretKey, useSelectedNetwork } from "@umami/state";
import { type Network, makeToolkit } from "@umami/tezos";
import { useCustomToast } from "@umami/utils";
import { FormProvider, useForm, useFormContext } from "react-hook-form";
import { Button, Input, Label, Text, View, YStack } from "tamagui";

import { forIDP } from "../../services/auth";

export const SignButton = ({
signer,
onSubmit,
isLoading: externalIsLoading,
isDisabled,
text = "Confirm Transaction",
network: preferredNetwork,
}: {
onSubmit: (tezosToolkit: TezosToolkit) => Promise<BatchWalletOperation | void>;
signer: ImplicitAccount;
isLoading?: boolean;
isDisabled?: boolean;
text?: string; // TODO: after FillStep migration change to the header value from SignPage
network?: Network;
}) => {
const form = useForm<{ password: string }>({ mode: "onBlur", defaultValues: { password: "" } });
const {
handleSubmit,
formState: { errors, isValid: isPasswordValid },
} = form;
let network = useSelectedNetwork();
if (preferredNetwork) {
network = preferredNetwork;
}

const {
formState: { isValid: isOuterFormValid },
} = useFormContext();

const isButtonDisabled = isDisabled || !isPasswordValid || !isOuterFormValid;

const getSecretKey = useGetSecretKey();
const toast = useCustomToast();
const { isLoading: internalIsLoading, handleAsyncAction } = useAsyncActionHandler();
const isLoading = internalIsLoading || externalIsLoading;

const onMnemonicSign = async ({ password }: { password: string }) =>
handleAsyncAction(async () => {
const secretKey = await getSecretKey(signer as MnemonicAccount, password);
return onSubmit(await makeToolkit({ type: "mnemonic", secretKey, network }));
});

const onSecretKeySign = async ({ password }: { password: string }) =>
handleAsyncAction(async () => {
const secretKey = await getSecretKey(signer as SecretKeyAccount, password);
return onSubmit(await makeToolkit({ type: "secret_key", secretKey, network }));
});

const onSocialSign = async () =>
handleAsyncAction(async () => {
const { secretKey } = await forIDP((signer as SocialAccount).idp).getCredentials();
return onSubmit(await makeToolkit({ type: "social", secretKey, network }));
});

const onLedgerSign = async () =>
handleAsyncAction(
async () => {
toast({
id: "ledger-sign-toast",
description: "Please approve the operation on your Ledger",
status: "info",
duration: 60000,
isClosable: true,
});
return onSubmit(
await makeToolkit({
type: "ledger",
account: signer as LedgerAccount,
network,
})
);
},
(error: any) => ({
description: `${error.message} Please connect your ledger, open Tezos app and try submitting transaction again`,
status: "error",
})
).finally(() => toast.close("ledger-sign-toast"));

switch (signer.type) {
case "secret_key":
case "mnemonic":
return (
<View width="full">
<FormProvider {...form}>
<YStack alignItems="start" spacing="30px">
<YStack isInvalid={!!errors.password}>
<Label>Password</Label>
<Input
data-testid="password"
type="password"
{...form.register("password", { required: "Password is required" })}
/>
{errors.password && <Text>{errors.password.message}</Text>}
</YStack>
<Button
width="full"
isDisabled={isButtonDisabled}
isLoading={isLoading}
onClick={handleSubmit(
signer.type === "mnemonic" ? onMnemonicSign : onSecretKeySign
)}
size="lg"
type="submit"
variant="primary"
>
{text}
</Button>
</YStack>
</FormProvider>
</View>
);
case "social":
return (
<Button
width="full"
isDisabled={isDisabled}
isLoading={isLoading}
onPress={onSocialSign}
size="lg"
variant="primary"
>
{text}
</Button>
);
case "ledger":
return (
<Button
width="full"
isDisabled={isDisabled}
isLoading={isLoading}
onClick={onLedgerSign}
size="lg"
variant="primary"
>
{text}
</Button>
);
}
};
67 changes: 67 additions & 0 deletions apps/mobile/components/SendFlow/SuccessStep/SuccessStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Check, ExternalLink } from "@tamagui/lucide-icons";
import { useSelectedNetwork } from "@umami/state";
import * as Linking from "expo-linking";
import { useRouter } from "expo-router";
import { Button, Dialog, Text, YStack } from "tamagui";

type SuccessStepProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
hash: string;
};

export const SuccessStep = ({ open, onOpenChange, hash }: SuccessStepProps) => {
const network = useSelectedNetwork();
const router = useRouter();
const tzktUrl = `${network.tzktExplorerUrl}/${hash}`;

const handleViewOperations = () => {
onOpenChange(false);
router.push("/home");
};

const handleViewInTzkt = async () => {
await Linking.openURL(tzktUrl);
};

return (
<Dialog modal onOpenChange={onOpenChange} open={open}>
<Dialog.Portal>
<Dialog.Overlay key="overlay" />

<Dialog.Content
key="content"
padding="$4"
animation="quick"
bordered
elevate
enterStyle={{ opacity: 0, scale: 0.95 }}
exitStyle={{ opacity: 0, scale: 0.95 }}
>
<YStack alignItems="center" space="$4">
<Check color="$green10" size={24} />

<Dialog.Title textAlign="center" size="$8">
Operation Submitted
</Dialog.Title>

<Text color="$gray11" textAlign="center" size="$5">
You can follow this operation's progress in the Operations section.{"\n"}
It may take up to 30 seconds to appear.
</Text>

<YStack width="100%" space="$3">
<Button onPress={handleViewOperations} size="$4" theme="active">
See all Operations
</Button>

<Button icon={ExternalLink} onPress={handleViewInTzkt} size="$4" variant="outlined">
View in TzKT
</Button>
</YStack>
</YStack>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
);
};
1 change: 1 addition & 0 deletions apps/mobile/components/SendFlow/SuccessStep/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SuccessStep";
Loading

1 comment on commit a72756c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title Lines Statements Branches Functions
apps/desktop Coverage: 83%
83.74% (1788/2135) 79.43% (850/1070) 78.27% (454/580)
apps/web Coverage: 83%
83.74% (1788/2135) 79.43% (850/1070) 78.27% (454/580)
packages/components Coverage: 97%
97.51% (196/201) 95.91% (94/98) 88.13% (52/59)
packages/core Coverage: 81%
82.37% (215/261) 72.51% (95/131) 81.66% (49/60)
packages/crypto Coverage: 100%
100% (43/43) 90.9% (10/11) 100% (7/7)
packages/data-polling Coverage: 96%
94.66% (142/150) 87.5% (21/24) 92.85% (39/42)
packages/multisig Coverage: 98%
98.47% (129/131) 85.71% (18/21) 100% (36/36)
packages/social-auth Coverage: 95%
95.45% (21/22) 91.66% (11/12) 100% (3/3)
packages/state Coverage: 83%
83.21% (833/1001) 79.58% (191/240) 76.7% (303/395)
packages/tezos Coverage: 89%
88.72% (118/133) 94.59% (35/37) 86.84% (33/38)
packages/tzkt Coverage: 89%
87.32% (62/71) 87.5% (14/16) 80.48% (33/41)

Please sign in to comment.