Skip to content

Commit 22d1ec0

Browse files
♻️ (signer-solana): Improve solana error handling
1 parent 47e18d4 commit 22d1ec0

21 files changed

+292
-265
lines changed

.changeset/seven-paws-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ledgerhq/device-signer-kit-solana": patch
3+
---
4+
5+
Improve error handling

packages/signer/signer-solana/src/api/app-binder/GetAddressDeviceActionTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import {
77
} from "@ledgerhq/device-management-kit";
88

99
import { type PublicKey } from "@api/model/PublicKey";
10+
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/SolanaApplicationErrors";
1011

1112
type GetAddressDAUserInteractionRequired =
1213
| UserInteractionRequired.None
1314
| UserInteractionRequired.VerifyAddress;
1415

1516
export type GetAddressDAOutput = SendCommandInAppDAOutput<PublicKey>;
16-
export type GetAddressDAError = SendCommandInAppDAError<never>;
17+
export type GetAddressDAError = SendCommandInAppDAError<SolanaAppErrorCodes>;
1718
export type GetAddressDAIntermediateValue =
1819
SendCommandInAppDAIntermediateValue<GetAddressDAUserInteractionRequired>;
1920

packages/signer/signer-solana/src/api/app-binder/GetAppConfigurationDeviceActionTypes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import {
77
} from "@ledgerhq/device-management-kit";
88

99
import { type AppConfiguration } from "@api/model/AppConfiguration";
10+
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/SolanaApplicationErrors";
1011

1112
export type GetAppConfigurationDAOutput =
1213
SendCommandInAppDAOutput<AppConfiguration>;
1314

14-
export type GetAppConfigurationDAError = SendCommandInAppDAError<never>;
15+
export type GetAppConfigurationDAError =
16+
SendCommandInAppDAError<SolanaAppErrorCodes>;
1517

1618
export type GetAppConfigurationDAIntermediateValue =
1719
SendCommandInAppDAIntermediateValue<UserInteractionRequired.None>;

packages/signer/signer-solana/src/api/app-binder/SignMessageDeviceActionTypes.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {
2-
type CommandErrorResult,
32
type DeviceActionState,
43
type ExecuteDeviceActionReturnType,
54
type OpenAppDAError,
65
type OpenAppDARequiredInteraction,
6+
type SendCommandInAppDAError,
77
type UserInteractionRequired,
88
} from "@ledgerhq/device-management-kit";
99

1010
import { type Signature } from "@api/model/Signature";
11+
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/SolanaApplicationErrors";
1112

1213
export type SignMessageDAOutput = Signature;
1314

@@ -16,7 +17,9 @@ export type SignMessageDAInput = {
1617
readonly message: string;
1718
};
1819

19-
export type SignMessageDAError = OpenAppDAError | CommandErrorResult["error"];
20+
export type SignMessageDAError =
21+
| OpenAppDAError
22+
| SendCommandInAppDAError<SolanaAppErrorCodes>;
2023

2124
type SignMessageDARequiredInteraction =
2225
| OpenAppDARequiredInteraction

packages/signer/signer-solana/src/api/app-binder/SignTransactionDeviceActionTypes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import {
2-
type CommandErrorResult,
32
type DeviceActionState,
43
type ExecuteDeviceActionReturnType,
54
type OpenAppDAError,
65
type OpenAppDARequiredInteraction,
6+
type SendCommandInAppDAError,
77
type UserInteractionRequired,
88
} from "@ledgerhq/device-management-kit";
99

1010
import { type Signature } from "@api/model/Signature";
1111
import { type Transaction } from "@api/model/Transaction";
1212
import { type TransactionOptions } from "@api/model/TransactionOptions";
13+
import { type SolanaAppErrorCodes } from "@internal/app-binder/command/utils/SolanaApplicationErrors";
1314

1415
export type SignTransactionDAOutput = Signature;
1516

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

2223
export type SignTransactionDAError =
2324
| OpenAppDAError
24-
| CommandErrorResult["error"];
25+
| SendCommandInAppDAError<SolanaAppErrorCodes>;
2526

2627
type SignTransactionDARequiredInteraction =
2728
| OpenAppDARequiredInteraction

packages/signer/signer-solana/src/internal/app-binder/command/GetAppConfigurationCommand.test.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,9 @@ describe("GetAppConfigurationCommand", () => {
8484
if (!isSuccessCommandResult(result)) {
8585
expect(result.error).toEqual(
8686
expect.objectContaining({
87-
_tag: "InvalidStatusWordError",
88-
originalError: expect.objectContaining({
89-
message: "Invalid response",
90-
}),
87+
_tag: "SolanaAppCommandError",
88+
errorCode: "6a82",
89+
message: "Invalid off-chain message format",
9190
}),
9291
);
9392
} else {
@@ -105,10 +104,9 @@ describe("GetAppConfigurationCommand", () => {
105104
if (!isSuccessCommandResult(result)) {
106105
expect(result.error).toEqual(
107106
expect.objectContaining({
108-
_tag: "InvalidStatusWordError",
109-
originalError: expect.objectContaining({
110-
message: "Invalid response",
111-
}),
107+
_tag: "SolanaAppCommandError",
108+
errorCode: "6a82",
109+
message: "Invalid off-chain message format",
112110
}),
113111
);
114112
} else {

packages/signer/signer-solana/src/internal/app-binder/command/GetAppConfigurationCommand.ts

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,34 @@ import {
77
type CommandResult,
88
CommandResultFactory,
99
InvalidStatusWordError,
10-
isCommandErrorCode,
1110
} from "@ledgerhq/device-management-kit";
11+
import { CommandErrorHelper } from "@ledgerhq/signer-utils";
12+
import { Maybe } from "purify-ts";
1213

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

1617
import {
17-
SolanaAppCommandError,
18-
solanaAppErrors,
19-
} from "./utils/solanaAppErrors";
18+
SOLANA_APP_ERRORS,
19+
SolanaAppCommandErrorFactory,
20+
type SolanaAppErrorCodes,
21+
} from "./utils/SolanaApplicationErrors";
2022

2123
type GetAppConfigurationCommandArgs = void;
2224

2325
export class GetAppConfigurationCommand
24-
implements Command<AppConfiguration, GetAppConfigurationCommandArgs>
26+
implements
27+
Command<
28+
AppConfiguration,
29+
GetAppConfigurationCommandArgs,
30+
SolanaAppErrorCodes
31+
>
2532
{
33+
private readonly errorHelper = new CommandErrorHelper<
34+
AppConfiguration,
35+
SolanaAppErrorCodes
36+
>(SOLANA_APP_ERRORS, SolanaAppCommandErrorFactory);
37+
2638
args: GetAppConfigurationCommandArgs;
2739

2840
constructor(args: GetAppConfigurationCommandArgs) {
@@ -38,40 +50,36 @@ export class GetAppConfigurationCommand
3850
}).build();
3951
}
4052

41-
parseResponse(response: ApduResponse): CommandResult<AppConfiguration> {
42-
const parser = new ApduParser(response);
43-
const errorCode = parser.encodeToHexaString(response.statusCode);
44-
if (isCommandErrorCode(errorCode, solanaAppErrors)) {
45-
return CommandResultFactory({
46-
error: new SolanaAppCommandError({
47-
...solanaAppErrors[errorCode],
48-
errorCode,
49-
}),
50-
});
51-
}
53+
parseResponse(
54+
response: ApduResponse,
55+
): CommandResult<AppConfiguration, SolanaAppErrorCodes> {
56+
return Maybe.fromNullable(
57+
this.errorHelper.getError(response),
58+
).orDefaultLazy(() => {
59+
const parser = new ApduParser(response);
60+
const buffer = parser.extractFieldByLength(5);
61+
if (
62+
!buffer ||
63+
buffer.length !== 5 ||
64+
buffer.some((element) => element === undefined)
65+
) {
66+
return CommandResultFactory({
67+
error: new InvalidStatusWordError("Invalid response"),
68+
});
69+
}
70+
71+
const config: AppConfiguration = {
72+
blindSigningEnabled: Boolean(buffer[0]),
73+
pubKeyDisplayMode:
74+
buffer[1] === 0
75+
? PublicKeyDisplayMode.LONG
76+
: PublicKeyDisplayMode.SHORT,
77+
version: `${buffer[2]}.${buffer[3]}.${buffer[4]}`,
78+
};
5279

53-
const buffer = parser.extractFieldByLength(5);
54-
if (
55-
!buffer ||
56-
buffer.length !== 5 ||
57-
buffer.some((element) => element === undefined)
58-
) {
5980
return CommandResultFactory({
60-
error: new InvalidStatusWordError("Invalid response"),
81+
data: config,
6182
});
62-
}
63-
64-
const config: AppConfiguration = {
65-
blindSigningEnabled: Boolean(buffer[0]),
66-
pubKeyDisplayMode:
67-
buffer[1] === 0
68-
? PublicKeyDisplayMode.LONG
69-
: PublicKeyDisplayMode.SHORT,
70-
version: `${buffer[2]}.${buffer[3]}.${buffer[4]}`,
71-
};
72-
73-
return CommandResultFactory({
74-
data: config,
7583
});
7684
}
7785
}

packages/signer/signer-solana/src/internal/app-binder/command/GetPubKeyCommand.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,9 @@ describe("GetPubKeyCommand", () => {
9696
if (!isSuccessCommandResult(result)) {
9797
expect(result.error).toEqual(
9898
expect.objectContaining({
99-
_tag: "InvalidStatusWordError",
100-
originalError: expect.objectContaining({
101-
message: "Public key is missing",
102-
}),
99+
_tag: "SolanaAppCommandError",
100+
errorCode: "6a82",
101+
message: "Invalid off-chain message format",
103102
}),
104103
);
105104
} else {

packages/signer/signer-solana/src/internal/app-binder/command/GetPubKeyCommand.ts

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ import {
88
type CommandResult,
99
CommandResultFactory,
1010
InvalidStatusWordError,
11-
isCommandErrorCode,
1211
} from "@ledgerhq/device-management-kit";
13-
import { DerivationPathUtils } from "@ledgerhq/signer-utils";
12+
import {
13+
CommandErrorHelper,
14+
DerivationPathUtils,
15+
} from "@ledgerhq/signer-utils";
1416
import bs58 from "bs58";
17+
import { Maybe } from "purify-ts";
1518

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

1821
import {
19-
SolanaAppCommandError,
20-
solanaAppErrors,
21-
} from "./utils/solanaAppErrors";
22+
SOLANA_APP_ERRORS,
23+
SolanaAppCommandErrorFactory,
24+
type SolanaAppErrorCodes,
25+
} from "./utils/SolanaApplicationErrors";
2226

2327
const PUBKEY_LENGTH = 32;
2428

@@ -29,8 +33,14 @@ type GetPubKeyCommandArgs = {
2933
};
3034

3135
export class GetPubKeyCommand
32-
implements Command<GetPubKeyCommandResponse, GetPubKeyCommandArgs>
36+
implements
37+
Command<GetPubKeyCommandResponse, GetPubKeyCommandArgs, SolanaAppErrorCodes>
3338
{
39+
private readonly errorHelper = new CommandErrorHelper<
40+
GetPubKeyCommandResponse,
41+
SolanaAppErrorCodes
42+
>(SOLANA_APP_ERRORS, SolanaAppCommandErrorFactory);
43+
3444
args: GetPubKeyCommandArgs;
3545

3646
constructor(args: GetPubKeyCommandArgs) {
@@ -58,33 +68,28 @@ export class GetPubKeyCommand
5868

5969
parseResponse(
6070
response: ApduResponse,
61-
): CommandResult<GetPubKeyCommandResponse> {
62-
const parser = new ApduParser(response);
63-
const errorCode = parser.encodeToHexaString(response.statusCode);
64-
if (isCommandErrorCode(errorCode, solanaAppErrors)) {
65-
return CommandResultFactory({
66-
error: new SolanaAppCommandError({
67-
...solanaAppErrors[errorCode],
68-
errorCode,
69-
}),
70-
});
71-
}
71+
): CommandResult<GetPubKeyCommandResponse, SolanaAppErrorCodes> {
72+
return Maybe.fromNullable(
73+
this.errorHelper.getError(response),
74+
).orDefaultLazy(() => {
75+
const parser = new ApduParser(response);
7276

73-
if (parser.testMinimalLength(PUBKEY_LENGTH) === false) {
74-
return CommandResultFactory({
75-
error: new InvalidStatusWordError("Public key is missing"),
76-
});
77-
}
77+
if (parser.testMinimalLength(PUBKEY_LENGTH) === false) {
78+
return CommandResultFactory({
79+
error: new InvalidStatusWordError("Public key is missing"),
80+
});
81+
}
82+
83+
const buffer = parser.extractFieldByLength(PUBKEY_LENGTH);
84+
if (buffer === undefined) {
85+
return CommandResultFactory({
86+
error: new InvalidStatusWordError("Unable to extract public key"),
87+
});
88+
}
7889

79-
const buffer = parser.extractFieldByLength(PUBKEY_LENGTH);
80-
if (buffer === undefined) {
8190
return CommandResultFactory({
82-
error: new InvalidStatusWordError("Unable to extract public key"),
91+
data: bs58.encode(buffer),
8392
});
84-
}
85-
86-
return CommandResultFactory({
87-
data: bs58.encode(buffer),
8893
});
8994
}
9095
}

packages/signer/signer-solana/src/internal/app-binder/command/SignOffChainMessageCommand.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@ describe("SignOffChainMessageCommand", () => {
7070
if (!isSuccessCommandResult(result)) {
7171
expect(result.error).toEqual(
7272
expect.objectContaining({
73-
_tag: "InvalidStatusWordError", // Adjust this based on your actual implementation
74-
originalError: expect.objectContaining({
75-
message: "Signature extraction failed",
76-
}),
73+
_tag: "SolanaAppCommandError",
74+
errorCode: "6a82",
75+
message: "Invalid off-chain message format",
7776
}),
7877
);
7978
} else {

0 commit comments

Comments
 (0)