Skip to content

Commit

Permalink
Merge pull request #222 from alephium/ledger-sign-unsigned-tx
Browse files Browse the repository at this point in the history
Support signing unsigned txs using ledger accounts
  • Loading branch information
Lbqds authored Oct 24, 2024
2 parents ba667d3 + 2d9992a commit 8779a71
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 161 deletions.
7 changes: 4 additions & 3 deletions packages/extension/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@
"User disconnected": "User disconnected",
"Transaction building failed": "Transaction building failed",
"Sending transaction failed": "Sending transaction failed",
"Sign unsigned tx failed": "Sign unsigned tx failed",
"Sign raw tx failed": "Sign raw tx failed",
"Sign message failed": "Sign message failed",
"Add Network": "Add Network",
"Switch Network": "Switch Network",
"Network ID": "Network ID",
Expand All @@ -206,8 +207,8 @@
"Sign Message": "Sign Message",
"Signer": "Signer",
"Hasher": "Hasher",
"Sign Unsigned TX": "Sign Unsigned TX",
"Unsigned TX": "Unsigned TX",
"Sign Raw TX": "Sign Raw TX",
"Raw TX": "Raw TX",
"The Ledger app is not connected": "The Ledger app is not connected",
"Insufficient token {{ tokenSymbol }}, expected at least {{ expectedStr }}, got {{ haveStr }}": "Insufficient token {{ tokenSymbol }}, expected at least {{ expectedStr }}, got {{ haveStr }}",
"No account found for network {{ networkId }}": "No account found for network {{ networkId }}",
Expand Down
6 changes: 3 additions & 3 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"@alephium/get-extension-wallet": "^1.7.3",
"@alephium/ledger-app": "0.6.0",
"@alephium/token-list": "0.0.19",
"@alephium/web3": "^1.7.3",
"@alephium/web3-test": "^1.7.3",
"@alephium/web3-wallet": "^1.7.3",
"@alephium/web3": "^1.8.3",
"@alephium/web3-test": "^1.8.3",
"@alephium/web3-wallet": "^1.8.3",
"@ledgerhq/hw-transport-webusb": "6.29.0",
"@ledgerhq/hw-transport-webhid": "6.29.0",
"@playwright/test": "^1.23.0",
Expand Down
63 changes: 43 additions & 20 deletions packages/extension/src/background/actionHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TransactionBuilder } from "@alephium/web3"
import { getAccounts } from "../shared/account/store"
import {
ActionItem,
Expand Down Expand Up @@ -74,26 +75,37 @@ export const handleActionApproval = async (
}

case "ALPH_SIGN_MESSAGE": {
const account = await wallet.getAccount({
address: action.payload.signerAddress,
networkId: action.payload.networkId,
})
if (!account) {
throw Error("No selected account")
}
try {
const account = await wallet.getAccount({
address: action.payload.signerAddress,
networkId: action.payload.networkId,
})
if (!account) {
throw Error("No selected account")
}
if (account.signer.type === 'ledger') {
throw Error("Signing messages with Ledger accounts is not supported")
}

const result = await wallet.signMessage(account, action.payload)
const result = await wallet.signMessage(account, action.payload)

return {
type: "ALPH_SIGN_MESSAGE_SUCCESS",
data: {
signature: result.signature,
actionHash,
},
return {
type: "ALPH_SIGN_MESSAGE_SUCCESS",
data: {
signature: result.signature,
actionHash,
},
}
} catch (error) {
return {
type: "ALPH_SIGN_MESSAGE_FAILURE",
data: { actionHash, error: `${error}` },
}
}
}

case "ALPH_SIGN_UNSIGNED_TX": {
const { signatureOpt } = additionalData as { signatureOpt: string | undefined }
try {
const account = await wallet.getAccount({
address: action.payload.signerAddress,
Expand All @@ -102,12 +114,23 @@ export const handleActionApproval = async (
if (!account) {
throw Error("No selected account")
}

const result = await wallet.signUnsignedTx(account, action.payload)

return {
type: "ALPH_SIGN_UNSIGNED_TX_SUCCESS",
data: { actionHash, result },
if (signatureOpt === undefined) {
const result = await wallet.signUnsignedTx(account, action.payload)

return {
type: "ALPH_SIGN_UNSIGNED_TX_SUCCESS",
data: { actionHash, result },
}
} else {
const signUnsignedTxResult = TransactionBuilder.buildUnsignedTx({
signerAddress: account.address,
unsignedTx: action.payload.unsignedTx
})
const result = { signature: signatureOpt, ...signUnsignedTxResult }
return {
type: "ALPH_SIGN_UNSIGNED_TX_SUCCESS",
data: { actionHash, result },
}
}
} catch (error) {
return {
Expand Down
35 changes: 23 additions & 12 deletions packages/extension/src/ui/features/actions/ActionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,26 @@ export const ActionScreen: FC = () => {
onSubmit={async () => {
await approveAction(action)
useAppState.setState({ isLoading: true })
await waitForMessage(
"ALPH_SIGN_MESSAGE_SUCCESS",
({ data }) => data.actionHash === action.meta.hash,
)
await analytics.track("signedMessage", {
networkId: selectedAccount?.networkId || t("unknown"),
})
closePopupIfLastAction()
useAppState.setState({ isLoading: false })
const result = await Promise.race([
waitForMessage(
'ALPH_SIGN_MESSAGE_SUCCESS',
({ data }) => data.actionHash === action.meta.hash,
),
waitForMessage(
'ALPH_SIGN_MESSAGE_FAILURE',
({ data }) => data.actionHash === action.meta.hash,
),
])
if ("error" in result) {
useAppState.setState({
error: `${t('Sign message failed')}: ${result.error}`,
isLoading: false,
})
navigate(routes.error())
} else {
closePopupIfLastAction()
useAppState.setState({ isLoading: false })
}
}}
onReject={onReject}
selectedAccount={signerAccount}
Expand All @@ -207,8 +218,8 @@ export const ActionScreen: FC = () => {
return (
<ApproveSignUnsignedTxScreen
params={action.payload}
onSubmit={async () => {
await approveAction(action)
onSubmit={async (signatureOpt) => {
await approveAction(action, signatureOpt)
useAppState.setState({ isLoading: true })
const result = await Promise.race([
waitForMessage(
Expand All @@ -222,7 +233,7 @@ export const ActionScreen: FC = () => {
])
if ("error" in result) {
useAppState.setState({
error: `${t('Sign unsigned tx failed')}: ${result.error}`,
error: `${t('Sign raw tx failed')}: ${result.error}`,
isLoading: false,
})
navigate(routes.error())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { binToHex, hexToBinUnsafe, SignUnsignedTxParams } from "@alephium/web3"
import { H6, H2, P4, CopyTooltip } from "@argent/ui"
import { FC } from "react"
import { FC, useCallback } from "react"

import { ConfirmPageProps } from "./DeprecatedConfirmScreen"
import { Box, Flex, VStack } from "@chakra-ui/react"
Expand All @@ -9,30 +9,71 @@ import { AccountNetworkInfo } from "./transaction/AccountNetworkInfo"
import blake from 'blakejs'
import { TxHashContainer } from "./TxHashContainer"
import { useTranslation } from "react-i18next"
import { LedgerStatus } from "./LedgerStatus"
import { useNavigate } from "react-router-dom"
import { useLedgerApp } from "../ledger/useLedgerApp"
import { getConfirmationTextByState } from "../ledger/types"

interface ApproveSignUnsignedTxScreenProps
extends Omit<ConfirmPageProps, "onSubmit"> {
params: SignUnsignedTxParams & { host: string }
onSubmit: (data: SignUnsignedTxParams) => void
onSubmit: (result: { signatureOpt: string | undefined }) => void
}

export const ApproveSignUnsignedTxScreen: FC<ApproveSignUnsignedTxScreenProps> = ({
params,
onSubmit,
onReject,
selectedAccount,
...props
}) => {
const { t } = useTranslation()
const txId = binToHex(blake.blake2b(hexToBinUnsafe(params.unsignedTx), undefined, 32))

const navigate = useNavigate()
const useLedger = selectedAccount !== undefined && selectedAccount.signer.type === "ledger"
const ledgerSubmit = useCallback((signature: string) => {
onSubmit({ signatureOpt: signature })
}, [onSubmit])
const { ledgerState, ledgerApp, ledgerSign } = useLedgerApp({
selectedAccount,
unsignedTx: params.unsignedTx,
onSubmit: ledgerSubmit,
navigate,
onReject
})

return (
<ConfirmScreen
confirmButtonText="Sign"
confirmButtonText={!useLedger ? t("Sign") : t(getConfirmationTextByState(ledgerState))}
confirmButtonDisabled={ledgerState !== undefined}
confirmButtonBackgroundColor="neutrals.800"
rejectButtonText="Cancel"
showHeader={false}
scrollable={false}
selectedAccount={selectedAccount}
onSubmit={() => onSubmit(params)}
onSubmit={() => {
if (useLedger) {
ledgerSign()
} else {
onSubmit({ signatureOpt: undefined })
}
}}
onReject={() => {
if (ledgerApp !== undefined) {
ledgerApp.close()
}
if (onReject !== undefined) {
onReject()
} else {
navigate(-1)
}
}}
footer={
<Flex direction="column" gap="1">
<LedgerStatus ledgerState={ledgerState} />
</Flex>
}
{...props}
>
{
Expand All @@ -45,7 +86,7 @@ export const ApproveSignUnsignedTxScreen: FC<ApproveSignUnsignedTxScreenProps> =
mt="3"
gap="6"
>
<H2>{t("Sign Unsigned TX")}</H2>
<H2>{t("Sign Raw TX")}</H2>
<H6>{params.host}</H6>
</Flex>
}
Expand All @@ -66,7 +107,7 @@ export const ApproveSignUnsignedTxScreen: FC<ApproveSignUnsignedTxScreenProps> =
w="full"
>
<P4 color="neutrals.300" fontWeight="bold">
{t("Unsigned TX")}
{t("Raw TX")}
</P4>
<P4
color="neutrals.400"
Expand Down
Loading

0 comments on commit 8779a71

Please sign in to comment.