Skip to content

Commit

Permalink
feat: hardware custom derivation paths
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Oct 6, 2024
1 parent 9a1c302 commit 7218313
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 36 deletions.
33 changes: 17 additions & 16 deletions src/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,7 @@ import { useWeb3Signer } from "../context/Web3";
import "../style/web3.scss";
import { formatError } from "../utils/errors";
import { cropString, isMobile } from "../utils/helper";

const connect = async (
notify: (type: string, message: string) => void,
connectProvider: (rdns: string) => Promise<void>,
provider: EIP6963ProviderInfo,
) => {
try {
await connectProvider(provider.rdns);
} catch (e) {
log.error(
`Provider connect to ${provider.rdns} failed: ${formatError(e)}`,
);
notify("error", `Wallet connection failed: ${formatError(e)}`);
}
};
import HardwareDerivationPaths, { connect } from "./HardwareDerivationPaths";

const Modal = ({
show,
Expand All @@ -43,6 +29,11 @@ const Modal = ({
const { t, notify } = useGlobalContext();
const { providers, connectProvider } = useWeb3Signer();

const [showDerivationPaths, setShowDerivationPaths] =
createSignal<boolean>(false);
const [hardwareProvider, setHardwareProvider] =
createSignal<EIP6963ProviderInfo>(undefined);

const Provider = ({ provider }: { provider: EIP6963ProviderInfo }) => {
return (
<div
Expand All @@ -52,6 +43,12 @@ const Modal = ({
return;
}

if (provider.isHardware) {
setHardwareProvider(provider);
setShowDerivationPaths(true);
return;
}

await connect(notify, connectProvider, provider);
}}>
<hr />
Expand All @@ -74,7 +71,6 @@ const Modal = ({

return (
<div
id="settings-menu"
class="frame assets-select"
onClick={() => setShow(false)}
style={show() ? "display: block;" : "display: none;"}>
Expand All @@ -93,6 +89,11 @@ const Modal = ({
{(item) => <Provider provider={item.info} />}
</For>
</div>
<HardwareDerivationPaths
show={showDerivationPaths}
provider={hardwareProvider}
setShow={setShowDerivationPaths}
/>
</div>
);
};
Expand Down
177 changes: 177 additions & 0 deletions src/components/HardwareDerivationPaths.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import log from "loglevel";
import { IoClose } from "solid-icons/io";
import { Accessor, For, Setter, createSignal } from "solid-js";

import type {
EIP6963ProviderDetail,
EIP6963ProviderInfo,
} from "../consts/Types";
import { useGlobalContext } from "../context/Global";
import { useWeb3Signer } from "../context/Web3";
import { formatError } from "../utils/errors";
import {
HardwareSigner,
derivationPaths,
} from "../utils/hardware/HadwareSigner";

export const connect = async (
notify: (type: string, message: string) => void,
connectProvider: (rdns: string) => Promise<void>,
provider: EIP6963ProviderInfo,
) => {
try {
await connectProvider(provider.rdns);
} catch (e) {
log.error(
`Provider connect to ${provider.rdns} failed: ${formatError(e)}`,
);
notify("error", `Wallet connection failed: ${formatError(e)}`);
}
};

const connectHardware = async (
notify: (type: string, message: string) => void,
connectProvider: (rdns: string) => Promise<void>,
provider: Accessor<EIP6963ProviderInfo>,
providers: Accessor<Record<string, EIP6963ProviderDetail>>,
path: string,
) => {
const hardwareProvider = provider();
const prov = providers()[hardwareProvider.rdns]
.provider as unknown as HardwareSigner;
prov.setDerivationPath(path);

await connect(notify, connectProvider, hardwareProvider);
};

const DerivationPath = ({
name,
path,
provider,
}: {
name: string;
path: string;
provider: Accessor<EIP6963ProviderInfo>;
}) => {
const { notify } = useGlobalContext();
const { connectProvider, providers } = useWeb3Signer();

return (
<div
class="provider-modal-entry-wrapper"
onClick={async () => {
await connectHardware(
notify,
connectProvider,
provider,
providers,
path,
);
}}>
<hr />
<div class="provider-modal-entry">
<h4>{name}</h4>
<span>{path}</span>
</div>
</div>
);
};

const CustomPath = ({
provider,
}: {
provider: Accessor<EIP6963ProviderInfo>;
}) => {
const { t, notify, hardwareDerivationPath, setHardwareDerivationPath } =
useGlobalContext();
const { connectProvider, providers } = useWeb3Signer();

const [path, setPath] = createSignal<string>(hardwareDerivationPath());

const updatePath = (input: HTMLInputElement) => {
setPath(input.value);
};

return (
<div>
<div
class="provider-modal-entry"
style={"cursor: default; padding-top: 0;"}>
<h4>Custom</h4>
<input
type="text"
value={path()}
data-testid="derivation-path"
placeholder={derivationPaths.Ethereum}
onInput={(e) => updatePath(e.currentTarget)}
onKeyUp={(e) => updatePath(e.currentTarget)}
onPaste={(e) => updatePath(e.currentTarget)}
/>
</div>

<div
class="provider-modal-entry"
style={"cursor: default; padding-top: 0;"}>
<button
class="btn"
style={"margin-top: 0;"}
disabled={path() === undefined || path() === ""}
onClick={async () => {
setHardwareDerivationPath(path());
await connectHardware(
notify,
connectProvider,
provider,
providers,
path(),
);
}}>
{t("submit_derivation_path")}
</button>
</div>
</div>
);
};

const HardwareDerivationPaths = ({
show,
setShow,
provider,
}: {
show: Accessor<boolean>;
setShow: Setter<boolean>;
provider: Accessor<EIP6963ProviderInfo>;
}) => {
const { t } = useGlobalContext();

return (
<div
class="frame assets-select"
onClick={() => setShow(false)}
style={show() ? "display: block;" : "display: none;"}>
<div onClick={(e) => e.stopPropagation()}>
<h2>{t("select_derivation_path")}</h2>
<span class="close" onClick={() => setShow(false)}>
<IoClose />
</span>
<hr class="spacer" />
<For
each={Object.entries(derivationPaths).sort(([a], [b]) =>
a.toLowerCase().localeCompare(b.toLowerCase()),
)}>
{([name, path]) => (
<DerivationPath
name={name}
path={path}
provider={provider}
/>
)}
</For>
<hr style={"margin-top: 0;"} />
<CustomPath provider={provider} />
</div>
</div>
);
};

export default HardwareDerivationPaths;
1 change: 1 addition & 0 deletions src/consts/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type EIP6963ProviderInfo = {
name: string;
icon?: string;
disabled?: boolean;
isHardware?: boolean;
};

export type EIP1193Provider = {
Expand Down
12 changes: 12 additions & 0 deletions src/context/Global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export type GlobalContextType = {
clearSwaps: () => Promise<any>;
updateSwapStatus: (id: string, newStatus: string) => Promise<boolean>;

hardwareDerivationPath: Accessor<string>;
setHardwareDerivationPath: Setter<string>;

setRdns: (address: string, rdns: string) => Promise<string>;
getRdnsForAddress: (address: string) => Promise<string | null>;
};
Expand Down Expand Up @@ -311,6 +314,13 @@ const GlobalProvider = (props: { children: any }) => {
},
);

const [hardwareDerivationPath, setHardwareDerivationPath] = makePersisted(
createSignal<string>(""),
{
name: "hardwareDerivationPath",
},
);

// i18n
createMemo(() => setI18n(i18nConfigured()));
const dictLocale = createMemo(() =>
Expand Down Expand Up @@ -381,6 +391,8 @@ const GlobalProvider = (props: { children: any }) => {

setRdns,
getRdnsForAddress,
hardwareDerivationPath,
setHardwareDerivationPath,
}}>
{props.children}
</GlobalContext.Provider>
Expand Down
6 changes: 4 additions & 2 deletions src/context/Web3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
import { config } from "../config";
import { RBTC } from "../consts/Assets";
import { EIP1193Provider, EIP6963ProviderDetail } from "../consts/Types";
import LedgerSigner from "../utils/LedgerSigner";
import TrezorSigner from "../utils/TrezorSigner";
import { Contracts, getContracts } from "../utils/boltzClient";
import LedgerSigner from "../utils/hardware/LedgerSigner";
import TrezorSigner from "../utils/hardware/TrezorSigner";
import { useGlobalContext } from "./Global";
import LedgerIcon from "/ledger.svg";
import TrezorIcon from "/trezor.svg";
Expand Down Expand Up @@ -71,6 +71,7 @@ const Web3SignerProvider = (props: {
uuid: "ledger",
rdns: "ledger",
icon: LedgerIcon,
isHardware: true,
disabled: navigator.hid === undefined,
},
},
Expand All @@ -81,6 +82,7 @@ const Web3SignerProvider = (props: {
uuid: "trezor",
rdns: "trezor",
icon: TrezorIcon,
isHardware: true,
},
},
});
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ const dict = {
insufficient_balance_line:
"You do not have enough balance in your wallet for this swap.",
select_wallet: "Select wallet",
select_derivation_path: "Select derivation path",
submit_derivation_path: "Submit",
switch_network: "Switch network",
block: "block",
logs_scan_progress: "Scan progress {{ value }}%",
Expand Down
10 changes: 10 additions & 0 deletions src/utils/hardware/HadwareSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const derivationPaths = {
RSK: "44'/137'/0'/0/0",
Ethereum: "44'/60'/0'/0/0",
};

interface HardwareSigner {
setDerivationPath(path: string): void;
}

export { HardwareSigner, derivationPaths };
Loading

0 comments on commit 7218313

Please sign in to comment.