Skip to content

Commit

Permalink
♻️ (signer-solana): Improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
fAnselmi-Ledger committed Jan 23, 2025
1 parent 47e18d4 commit fa7f949
Show file tree
Hide file tree
Showing 19 changed files with 300 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import {
} from "@ledgerhq/device-management-kit";

import { type PublicKey } from "@api/model/PublicKey";
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/newSolanaAppErrors";

type GetAddressDAUserInteractionRequired =
| UserInteractionRequired.None
| UserInteractionRequired.VerifyAddress;

export type GetAddressDAOutput = SendCommandInAppDAOutput<PublicKey>;
export type GetAddressDAError = SendCommandInAppDAError<never>;
export type GetAddressDAError = SendCommandInAppDAError<SolanaAppErrorCodes>;
export type GetAddressDAIntermediateValue =
SendCommandInAppDAIntermediateValue<GetAddressDAUserInteractionRequired>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
} from "@ledgerhq/device-management-kit";

import { type AppConfiguration } from "@api/model/AppConfiguration";
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/newSolanaAppErrors";

export type GetAppConfigurationDAOutput =
SendCommandInAppDAOutput<AppConfiguration>;

export type GetAppConfigurationDAError = SendCommandInAppDAError<never>;
export type GetAppConfigurationDAError =
SendCommandInAppDAError<SolanaAppErrorCodes>;

export type GetAppConfigurationDAIntermediateValue =
SendCommandInAppDAIntermediateValue<UserInteractionRequired.None>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
type CommandErrorResult,
type DeviceActionState,
type ExecuteDeviceActionReturnType,
type OpenAppDAError,
type OpenAppDARequiredInteraction,
type SendCommandInAppDAError,
type UserInteractionRequired,
} from "@ledgerhq/device-management-kit";

import { type Signature } from "@api/model/Signature";
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/newSolanaAppErrors";

export type SignMessageDAOutput = Signature;

Expand All @@ -16,7 +17,9 @@ export type SignMessageDAInput = {
readonly message: string;
};

export type SignMessageDAError = OpenAppDAError | CommandErrorResult["error"];
export type SignMessageDAError =
| OpenAppDAError
| SendCommandInAppDAError<SolanaAppErrorCodes>;

type SignMessageDARequiredInteraction =
| OpenAppDARequiredInteraction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
type CommandErrorResult,
type DeviceActionState,
type ExecuteDeviceActionReturnType,
type OpenAppDAError,
type OpenAppDARequiredInteraction,
type SendCommandInAppDAError,
type UserInteractionRequired,
} from "@ledgerhq/device-management-kit";

import { type Signature } from "@api/model/Signature";
import { type Transaction } from "@api/model/Transaction";
import { type TransactionOptions } from "@api/model/TransactionOptions";
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/newSolanaAppErrors";

export type SignTransactionDAOutput = Signature;

Expand All @@ -21,7 +22,7 @@ export type SignTransactionDAInput = {

export type SignTransactionDAError =
| OpenAppDAError
| CommandErrorResult["error"];
| SendCommandInAppDAError<SolanaAppErrorCodes>;

type SignTransactionDARequiredInteraction =
| OpenAppDARequiredInteraction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,9 @@ describe("GetAppConfigurationCommand", () => {
if (!isSuccessCommandResult(result)) {
expect(result.error).toEqual(
expect.objectContaining({
_tag: "InvalidStatusWordError",
originalError: expect.objectContaining({
message: "Invalid response",
}),
_tag: "SolanaAppCommandError",
errorCode: "6a82",
message: "Invalid off-chain message format",
}),
);
} else {
Expand All @@ -105,10 +104,9 @@ describe("GetAppConfigurationCommand", () => {
if (!isSuccessCommandResult(result)) {
expect(result.error).toEqual(
expect.objectContaining({
_tag: "InvalidStatusWordError",
originalError: expect.objectContaining({
message: "Invalid response",
}),
_tag: "SolanaAppCommandError",
errorCode: "6a82",
message: "Invalid off-chain message format",
}),
);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,34 @@ import {
type CommandResult,
CommandResultFactory,
InvalidStatusWordError,
isCommandErrorCode,
} from "@ledgerhq/device-management-kit";
import { CommandErrorHelper } from "@ledgerhq/signer-utils";
import { Maybe } from "purify-ts";

import { type AppConfiguration } from "@api/model/AppConfiguration";
import { PublicKeyDisplayMode } from "@api/model/PublicKeyDisplayMode";

import {
SolanaAppCommandError,
solanaAppErrors,
} from "./utils/solanaAppErrors";
SOLANA_APP_ERRORS,
SolanaAppCommandErrorFactory,
type SolanaAppErrorCodes,
} from "./utils/newSolanaAppErrors";

type GetAppConfigurationCommandArgs = void;

export class GetAppConfigurationCommand
implements Command<AppConfiguration, GetAppConfigurationCommandArgs>
implements
Command<
AppConfiguration,
GetAppConfigurationCommandArgs,
SolanaAppErrorCodes
>
{
private readonly errorHelper = new CommandErrorHelper<
AppConfiguration,
SolanaAppErrorCodes
>(SOLANA_APP_ERRORS, SolanaAppCommandErrorFactory);

args: GetAppConfigurationCommandArgs;

constructor(args: GetAppConfigurationCommandArgs) {
Expand All @@ -38,40 +50,44 @@ export class GetAppConfigurationCommand
}).build();
}

parseResponse(response: ApduResponse): CommandResult<AppConfiguration> {
const parser = new ApduParser(response);
const errorCode = parser.encodeToHexaString(response.statusCode);
if (isCommandErrorCode(errorCode, solanaAppErrors)) {
return CommandResultFactory({
error: new SolanaAppCommandError({
...solanaAppErrors[errorCode],
errorCode,
}),
});
}
parseResponse(
response: ApduResponse,
): CommandResult<AppConfiguration, SolanaAppErrorCodes> {
return Maybe.fromNullable(
this.errorHelper.getError(response),
).orDefaultLazy(() => {
const parser = new ApduParser(response);
const buffer = parser.extractFieldByLength(5);
if (
!buffer ||
buffer.length !== 5 ||
buffer.some((element) => element === undefined)
) {
return CommandResultFactory({
error: new InvalidStatusWordError("Invalid response"),
});
}

const config: AppConfiguration = {
blindSigningEnabled: Boolean(buffer[0]),
pubKeyDisplayMode:
buffer[1] === 0
? PublicKeyDisplayMode.LONG
: PublicKeyDisplayMode.SHORT,
version: `${buffer[2]}.${buffer[3]}.${buffer[4]}`,
};

const buffer = parser.extractFieldByLength(5);
if (
!buffer ||
buffer.length !== 5 ||
buffer.some((element) => element === undefined)
) {
return CommandResultFactory({
error: new InvalidStatusWordError("Invalid response"),
data: config,
});
}

const config: AppConfiguration = {
blindSigningEnabled: Boolean(buffer[0]),
pubKeyDisplayMode:
buffer[1] === 0
? PublicKeyDisplayMode.LONG
: PublicKeyDisplayMode.SHORT,
version: `${buffer[2]}.${buffer[3]}.${buffer[4]}`,
};

return CommandResultFactory({
data: config,
});
}
}

// return Maybe.fromNullable(
// this.errorHelper.getError(response),
// ).orDefaultLazy(() => {
// //
// //
// //
// });
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,9 @@ describe("GetPubKeyCommand", () => {
if (!isSuccessCommandResult(result)) {
expect(result.error).toEqual(
expect.objectContaining({
_tag: "InvalidStatusWordError",
originalError: expect.objectContaining({
message: "Public key is missing",
}),
_tag: "SolanaAppCommandError",
errorCode: "6a82",
message: "Invalid off-chain message format",
}),
);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import {
type CommandResult,
CommandResultFactory,
InvalidStatusWordError,
isCommandErrorCode,
} from "@ledgerhq/device-management-kit";
import { DerivationPathUtils } from "@ledgerhq/signer-utils";
import {
CommandErrorHelper,
DerivationPathUtils,
} from "@ledgerhq/signer-utils";
import bs58 from "bs58";
import { Maybe } from "purify-ts";

import { type PublicKey } from "@api/model/PublicKey";

import {
SolanaAppCommandError,
solanaAppErrors,
} from "./utils/solanaAppErrors";
SOLANA_APP_ERRORS,
SolanaAppCommandErrorFactory,
type SolanaAppErrorCodes,
} from "./utils/newSolanaAppErrors";

const PUBKEY_LENGTH = 32;

Expand All @@ -29,8 +33,14 @@ type GetPubKeyCommandArgs = {
};

export class GetPubKeyCommand
implements Command<GetPubKeyCommandResponse, GetPubKeyCommandArgs>
implements
Command<GetPubKeyCommandResponse, GetPubKeyCommandArgs, SolanaAppErrorCodes>
{
private readonly errorHelper = new CommandErrorHelper<
GetPubKeyCommandResponse,
SolanaAppErrorCodes
>(SOLANA_APP_ERRORS, SolanaAppCommandErrorFactory);

args: GetPubKeyCommandArgs;

constructor(args: GetPubKeyCommandArgs) {
Expand Down Expand Up @@ -58,33 +68,28 @@ export class GetPubKeyCommand

parseResponse(
response: ApduResponse,
): CommandResult<GetPubKeyCommandResponse> {
const parser = new ApduParser(response);
const errorCode = parser.encodeToHexaString(response.statusCode);
if (isCommandErrorCode(errorCode, solanaAppErrors)) {
return CommandResultFactory({
error: new SolanaAppCommandError({
...solanaAppErrors[errorCode],
errorCode,
}),
});
}
): CommandResult<GetPubKeyCommandResponse, SolanaAppErrorCodes> {
return Maybe.fromNullable(
this.errorHelper.getError(response),
).orDefaultLazy(() => {
const parser = new ApduParser(response);

if (parser.testMinimalLength(PUBKEY_LENGTH) === false) {
return CommandResultFactory({
error: new InvalidStatusWordError("Public key is missing"),
});
}
if (parser.testMinimalLength(PUBKEY_LENGTH) === false) {
return CommandResultFactory({
error: new InvalidStatusWordError("Public key is missing"),
});
}

const buffer = parser.extractFieldByLength(PUBKEY_LENGTH);
if (buffer === undefined) {
return CommandResultFactory({
error: new InvalidStatusWordError("Unable to extract public key"),
});
}

const buffer = parser.extractFieldByLength(PUBKEY_LENGTH);
if (buffer === undefined) {
return CommandResultFactory({
error: new InvalidStatusWordError("Unable to extract public key"),
data: bs58.encode(buffer),
});
}

return CommandResultFactory({
data: bs58.encode(buffer),
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ describe("SignOffChainMessageCommand", () => {
if (!isSuccessCommandResult(result)) {
expect(result.error).toEqual(
expect.objectContaining({
_tag: "InvalidStatusWordError", // Adjust this based on your actual implementation
originalError: expect.objectContaining({
message: "Signature extraction failed",
}),
_tag: "SolanaAppCommandError",
errorCode: "6a82",
message: "Invalid off-chain message format",
}),
);
} else {
Expand Down
Loading

0 comments on commit fa7f949

Please sign in to comment.