UNPKG

@ledgerhq/live-common

Version:
135 lines 5.34 kB
import { UserRefusedAddress } from "@ledgerhq/errors"; import { log } from "@ledgerhq/logs"; import invariant from "invariant"; import { useCallback, useEffect, useRef, useState } from "react"; import { firstValueFrom, from } from "rxjs"; import { AcreMessageType } from "@ledgerhq/wallet-api-acre-module"; import perFamily from "../../generated/hw-signMessage"; import { createAction as createAppAction } from "../actions/app"; import { withDevice } from "../deviceAccess"; import { messageSigner as ACREMessageSigner } from "../../families/bitcoin/ACRESetup"; import { decodeAccountId } from "../../account"; export const prepareMessageToSign = (account, message) => { const utf8Message = Buffer.from(message, "hex").toString(); if (!perFamily[account.currency.family]) { throw new Error("Crypto does not support signMessage"); } if ("prepareMessageToSign" in perFamily[account.currency.family]) { return perFamily[account.currency.family].prepareMessageToSign({ account, message: utf8Message, }); } // Default implementation return { message: utf8Message }; }; const signMessage = (transport, account, opts) => { const { currency } = account; let signMessage = perFamily[currency.family].signMessage; if ("type" in opts) { switch (opts.type) { case AcreMessageType.Withdraw: signMessage = ACREMessageSigner.signWithdraw; break; case AcreMessageType.SignIn: signMessage = ACREMessageSigner.signIn; break; default: signMessage = ACREMessageSigner.signMessage; break; } } invariant(signMessage, `signMessage is not implemented for ${currency.id}`); return signMessage(transport, account, opts) .then(result => { const path = "path" in opts && opts.path ? opts.path : account.freshAddressPath; log("hw", `signMessage ${currency.id} on ${path} with message [${opts.message}]`, result); return result; }) .catch(e => { const path = "path" in opts && opts.path ? opts.path : account.freshAddressPath; log("hw", `signMessage ${currency.id} on ${path} FAILED ${String(e)}`); if (e && e.name === "TransportStatusError") { if (e.statusCode === 0x6985 || e.statusCode === 0x5501) { throw new UserRefusedAddress(); } } throw e; }); }; export const signMessageExec = ({ request, deviceId }) => { if (!request.account) { throw new Error("account is required"); } const { type } = decodeAccountId(request.account.id); if (type === "mock") { return from(Promise.resolve({ signature: "mockedSignature", })); } const result = withDevice(deviceId)(transport => from(signMessage(transport, request.account, request.message))); return result; }; const initialState = { signMessageRequested: null, signMessageError: null, signMessageResult: null, }; export const createAction = (connectAppExec, signMessage = signMessageExec) => { const useHook = (reduxDevice, request) => { const appState = createAppAction(connectAppExec).useHook(reduxDevice, { appName: request.appName, dependencies: request.dependencies, account: request.isACRE ? undefined : request.account, // Bypass derivation check with ACRE as we can use other addresses than the freshest }); const { device, opened, inWrongDeviceForAccount, error } = appState; const [state, setState] = useState({ ...initialState, signMessageRequested: request.message, }); const signedFired = useRef(); const sign = useCallback(async () => { let result; if (!device) { setState({ ...initialState, signMessageError: new Error("no Device"), }); return; } try { result = await firstValueFrom(signMessage({ request, deviceId: device.deviceId, })); } catch (e) { if (e.name === "UserRefusedAddress") { e.name = "UserRefusedOnDevice"; e.message = "UserRefusedOnDevice"; } return setState({ ...initialState, signMessageError: e }); } setState({ ...initialState, signMessageResult: result?.signature }); }, [device, request]); useEffect(() => { if (!device || !opened || inWrongDeviceForAccount || error) { return; } if (state.signMessageRequested && !signedFired.current) { signedFired.current = true; sign(); } }, [device, opened, inWrongDeviceForAccount, error, sign, state.signMessageRequested]); return { ...appState, ...state }; }; return { useHook, mapResult: (state) => ({ signature: state.signMessageResult, error: state.signMessageError, }), }; }; export default signMessage; //# sourceMappingURL=index.js.map