Skip to content

Commit

Permalink
♻️ (signer-solana) [NO-ISSUE]: Improve error handling (#626)
Browse files Browse the repository at this point in the history
  • Loading branch information
fAnselmi-Ledger authored Jan 23, 2025
2 parents b9df458 + 22d1ec0 commit ac3edef
Show file tree
Hide file tree
Showing 21 changed files with 292 additions and 265 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-paws-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-solana": patch
---

Improve error handling
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/SolanaApplicationErrors";

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/SolanaApplicationErrors";

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/SolanaApplicationErrors";

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/SolanaApplicationErrors";

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/SolanaApplicationErrors";

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,36 @@ 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,
});
}
}
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/SolanaApplicationErrors";

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 ac3edef

Please sign in to comment.