Skip to content

Commit

Permalink
💄 (smpl): Custom device action input form for signpsbt
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabbech-ledger committed Jan 13, 2025
1 parent 30b39ad commit a7556f1
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@ledgerhq/device-management-kit";
import { Flex, Icons, Tag, Text, Tooltip } from "@ledgerhq/react-ui";
import styled from "styled-components";
import { inspect } from "util";

import { type FieldType } from "@/hooks/useForm";

Expand Down Expand Up @@ -91,11 +92,11 @@ export function DeviceActionResponse<
wordBreak: "break-word",
}}
>
{JSON.stringify(
isError ? props.error : props.deviceActionState,
null,
2,
)}
{isError
? inspect(props.error, { depth: null })
: props.deviceActionState.status === DeviceActionStatus.Error
? inspect(props.deviceActionState.error, { depth: null })
: JSON.stringify(props.deviceActionState, null, 2)}
</Text>
{!isError &&
props.deviceActionState.status === DeviceActionStatus.Pending ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export type DeviceActionProps<
debug?: boolean,
) => ExecuteDeviceActionReturnType<Output, Error, IntermediateValue>;
initialValues: Input;
InputValuesComponent?: React.FC<{
initialValues: Input;
onChange: (values: Input) => void;
valueSelector?: ValueSelector<FieldType>;
disabled?: boolean;
}>;
validateValues?: (args: Input) => boolean;
valueSelector?: ValueSelector<FieldType>;
deviceModelId: DeviceModelId;
Expand Down Expand Up @@ -94,6 +100,7 @@ export function DeviceActionTester<
executeDeviceAction,
valueSelector,
validateValues,
InputValuesComponent,
} = props;

const nonce = useRef(-1);
Expand Down Expand Up @@ -204,12 +211,21 @@ export function DeviceActionTester<
rowGap={3}
pointerEvents={loading ? "none" : "auto"}
>
<CommandForm
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
{InputValuesComponent ? (
<InputValuesComponent
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
) : (
<CommandForm
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
)}
<Divider />
<Switch
checked={inspect}
Expand Down
100 changes: 100 additions & 0 deletions apps/sample/src/components/SignerBtcView/SignPsbtDAInputValusForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useCallback, useEffect } from "react";
import { DefaultDescriptorTemplate } from "@ledgerhq/device-signer-kit-bitcoin";
import { Flex, Input, SelectInput, Text } from "@ledgerhq/react-ui";

import { useForm } from "@/hooks/useForm";

type SignPsbtInputValuesType = {
psbt: string;
path: string;
descriptorTemplate: DefaultDescriptorTemplate;
};

const descriptorTemplateToDerivationPath: Record<
DefaultDescriptorTemplate,
string
> = {
[DefaultDescriptorTemplate.TAPROOT]: "86'/0'/0'",
[DefaultDescriptorTemplate.NATIVE_SEGWIT]: "84'/0'/0'",
[DefaultDescriptorTemplate.NESTED_SEGWIT]: "49'/0'/0'",
[DefaultDescriptorTemplate.LEGACY]: "44'/0'/0'",
};

const descriptorTemplateToLabel = {
[DefaultDescriptorTemplate.TAPROOT]: "Taproot",
[DefaultDescriptorTemplate.NATIVE_SEGWIT]: "Native Segwit",
[DefaultDescriptorTemplate.NESTED_SEGWIT]: "Nested Segwit",
[DefaultDescriptorTemplate.LEGACY]: "Legacy",
};

export const SignPsbtDAInputValuesForm: React.FC<{
initialValues: SignPsbtInputValuesType;
onChange: (values: SignPsbtInputValuesType) => void;
disabled?: boolean;
}> = ({ initialValues, onChange, disabled }) => {
const { formValues, setFormValues, setFormValue } = useForm(initialValues);

useEffect(() => {
onChange(formValues);
}, [formValues, onChange]);

const onWalletDescriptorTemplateChange = useCallback(
(value: DefaultDescriptorTemplate) => {
const newValues = {
path: descriptorTemplateToDerivationPath[value],
descriptorTemplate: value,
};
setFormValues((prev) => ({ ...prev, ...newValues }));
},
[setFormValues],
);

return (
<Flex
flexDirection="column"
flex={1}
rowGap={6}
columnGap={6}
flexWrap="wrap"
>
<Flex flexDirection="row" alignItems="center" mb={4}>
<Text style={{ marginRight: 8 }}>Wallet address type</Text>
<SelectInput
options={Object.entries(DefaultDescriptorTemplate).map(
([_key, value]) => ({
label: descriptorTemplateToLabel[value],
value,
}),
)}
value={{
label: descriptorTemplateToLabel[formValues.descriptorTemplate],
value: formValues.descriptorTemplate,
}}
isMulti={false}
isSearchable={false}
onChange={(newVal) =>
newVal && onWalletDescriptorTemplateChange(newVal.value)
}
/>
</Flex>

<Input
id="path"
value={formValues.path}
placeholder="path"
onChange={(newVal) => setFormValue("path", newVal)}
disabled={disabled}
data-testid="input-text_path"
/>

<Input
id="psbt"
value={formValues.psbt}
placeholder="psbt"
onChange={(newVal) => setFormValue("psbt", newVal)}
disabled={disabled}
data-testid="input-text_psbt"
/>
</Flex>
);
};
15 changes: 8 additions & 7 deletions apps/sample/src/components/SignerBtcView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {

import { DeviceActionsList } from "@/components/DeviceActionsView/DeviceActionsList";
import { type DeviceActionProps } from "@/components/DeviceActionsView/DeviceActionTester";
import { SignPsbtDAInputValuesForm } from "@/components/SignerBtcView/SignPsbtDAInputValusForm";
import { useDmk } from "@/providers/DeviceManagementKitProvider";

const DEFAULT_DERIVATION_PATH = "84'/0'/0'";
Expand Down Expand Up @@ -87,29 +88,29 @@ export const SignerBtcView: React.FC<{ sessionId: string }> = ({
title: "Sign psbt",
description:
"Perform all the actions necessary to sign a PSBT with the device",
executeDeviceAction: ({ derivationPath, psbt }) => {
executeDeviceAction: ({ descriptorTemplate, psbt, path }) => {
if (!signer) {
throw new Error("Signer not initialized");
}

return signer.signPsbt(
new DefaultWallet(
derivationPath,
DefaultDescriptorTemplate.NATIVE_SEGWIT,
),
new DefaultWallet(path, descriptorTemplate),
psbt,
);
},
InputValuesComponent: SignPsbtDAInputValuesForm,
initialValues: {
derivationPath: DEFAULT_DERIVATION_PATH,
descriptorTemplate: DefaultDescriptorTemplate.NATIVE_SEGWIT,
psbt: "",
path: DEFAULT_DERIVATION_PATH,
},
deviceModelId,
} satisfies DeviceActionProps<
SignPsbtDAOutput,
{
psbt: string;
derivationPath: string;
path: string;
descriptorTemplate: DefaultDescriptorTemplate;
},
SignPsbtDAError,
SignPsbtDAIntermediateValue
Expand Down
1 change: 1 addition & 0 deletions apps/sample/src/hooks/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export function useForm<T extends Record<string, string | boolean | number>>(
return {
formValues,
setFormValue,
setFormValues,
};
}
2 changes: 1 addition & 1 deletion apps/sample/src/styles/globalstyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const GlobalStyle = createGlobalStyle`
background-color: #000000;
}
body {
user-select: none;
user-select: text;
}
.no-scrollbar {
&::-webkit-scrollbar {
Expand Down

0 comments on commit a7556f1

Please sign in to comment.