Skip to content

Commit

Permalink
✨ (context-module): Update ProvideGenericContext task with new specif…
Browse files Browse the repository at this point in the history
…ication
  • Loading branch information
paoun-ledger committed Nov 28, 2024
1 parent fa9c3d6 commit ba98481
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ProvideEnumCommand
getApdu(): Apdu {
const ProvideEnumArgs: ApduBuilderArgs = {
cla: 0xe0,
ins: 0x99, // FIXME: TBD
ins: 0x24,
p1: this.args.isFirstChunk ? 0x01 : 0x00,
p2: 0x00,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class ProvideTransactionFieldDescriptionCommand
getApdu(): Apdu {
const ProvideTransactionFieldDescriptionArgs: ApduBuilderArgs = {
cla: 0xe0,
ins: 0x99, // FIXME: TBD
ins: 0x28,
p1: this.args.isFirstChunk ? 0x01 : 0x00,
p2: 0x00,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class ProvideTransactionInformationCommand
getApdu(): Apdu {
const ProvideTransactionInformationArgs: ApduBuilderArgs = {
cla: 0xe0,
ins: 0x99, // FIXME: TBD
ins: 0x26,
p1: this.args.isFirstChunk ? 0x01 : 0x00,
p2: 0x00,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import {
type ClearSignContextReference,
type ClearSignContextSuccess,
ClearSignContextType,
type ContextModule,
} from "@ledgerhq/context-module";
import {
bufferToHexaString,
type CommandErrorResult,
type CommandResult,
CommandResultFactory,
CommandResultStatus,
type InternalApi,
InvalidStatusWordError,
isSuccessCommandResult,
} from "@ledgerhq/device-management-kit";
import { Just, type Maybe, Nothing } from "purify-ts";

import { GetChallengeCommand } from "@internal/app-binder/command/GetChallengeCommand";
import { ProvideEnumCommand } from "@internal/app-binder/command/ProvideEnumCommand";
import {
ProvideNFTInformationCommand,
Expand All @@ -24,29 +29,30 @@ import {
import { ProvideTransactionFieldDescriptionCommand } from "@internal/app-binder/command/ProvideTransactionFieldDescriptionCommand";
import { ProvideTransactionInformationCommand } from "@internal/app-binder/command/ProvideTransactionInformationCommand";
import { ProvideTrustedNameCommand } from "@internal/app-binder/command/ProvideTrustedNameCommand";
import {
SetPluginCommand,
type SetPluginCommandErrorCodes,
} from "@internal/app-binder/command/SetPluginCommand";
import { StoreTransactionCommand } from "@internal/app-binder/command/StoreTransactionCommand";
import { PayloadUtils } from "@internal/shared/utils/PayloadUtils";
import { type TransactionParserService } from "@internal/transaction/service/parser/TransactionParserService";

import {
SendCommandInChunksTask,
type SendCommandInChunksTaskArgs,
} from "./SendCommandInChunksTask";

export type GenericContext = {
readonly transactionInfo: string;
readonly transactionFields: ClearSignContextSuccess[];
};

export type ProvideTransactionGenericContextTaskArgs = {
serializedTransaction: Uint8Array;
transactionInfo: Uint8Array;
transactionFieldDescription: Record<string, string>;
metadatas: Record<string, ClearSignContextSuccess>;
readonly contextModule: ContextModule;
readonly transactionParser: TransactionParserService;
readonly chainId: number;
readonly serializedTransaction: Uint8Array;
readonly context: GenericContext;
};

export type ProvideTransactionGenericContextTaskErrorCodes =
| void
| SetPluginCommandErrorCodes
| ProvideNFTInformationCommandErrorCodes;
void | ProvideNFTInformationCommandErrorCodes;

export class ProvideTransactionGenericContextTask {
constructor(
Expand All @@ -72,38 +78,31 @@ export class ProvideTransactionGenericContextTask {
}

// Provide the transaction information
const transactionInfoResult = await new SendCommandInChunksTask(this.api, {
data: this.args.transactionInfo,
commandFactory: (args) =>
const transactionInfoResult = await this.sendInChunks(
this.args.context.transactionInfo,
(args) =>
new ProvideTransactionInformationCommand({
data: args.chunkedData,
isFirstChunk: args.isFirstChunk,
}),
}).run();
);

if (!isSuccessCommandResult(transactionInfoResult)) {
return Just(transactionInfoResult);
}

// Provide the transaction field description and metadata
// The metadata should be provided first if it exists
for (const key of Object.keys(this.args.transactionFieldDescription)) {
if (this.args.metadatas[key]) {
const metadata = this.args.metadatas[key];
const metadataResult = await this.provideContext(metadata);

if (!isSuccessCommandResult(metadataResult)) {
return Just(metadataResult);
// Provide the transaction field description and according metadata reference
for (const field of this.args.context.transactionFields) {
if (field.reference !== undefined) {
const provideReferenceResult = await this.provideContextReference(
field.reference,
);
if (provideReferenceResult.isJust()) {
return provideReferenceResult;
}
}

const transactionFieldResult = await this.provideContext({
type: ClearSignContextType.TRANSACTION_FIELD_DESCRIPTION,
// key is a keyof typeof this.args.transactionFieldDescription
// so it is safe to use it as a key to access the value of the object
payload: this.args.transactionFieldDescription[key]!,
});

const transactionFieldResult = await this.provideContext({ ...field });
if (!isSuccessCommandResult(transactionFieldResult)) {
return Just(transactionFieldResult);
}
Expand All @@ -112,6 +111,58 @@ export class ProvideTransactionGenericContextTask {
return Nothing;
}

async provideContextReference(
reference: ClearSignContextReference,
): Promise<
Maybe<CommandErrorResult<ProvideTransactionGenericContextTaskErrorCodes>>
> {
const values = this.args.transactionParser.extractValue(
this.args.serializedTransaction,
reference.valuePath,
);
if (values.isLeft()) {
return Just({
status: CommandResultStatus.Error,
error: new InvalidStatusWordError(
"The clear sign context reference contains a path not found in that transaction",
),
});
}
for (const value of values.unsafeCoerce()) {
const address = bufferToHexaString(value.slice(0, 20));
let context;
if (reference.type === ClearSignContextType.TRUSTED_NAME) {
const getChallengeResult = await this.api.sendCommand(
new GetChallengeCommand(),
);
if (!isSuccessCommandResult(getChallengeResult)) {
return Just(getChallengeResult);
}
context = await this.args.contextModule.getContext({
type: reference.type,
chainId: this.args.chainId,
address,
challenge: getChallengeResult.data.challenge,
types: reference.types,
sources: reference.sources,
});
} else {
context = await this.args.contextModule.getContext({
type: reference.type,
chainId: this.args.chainId,
address,
});
}
if (context.type !== ClearSignContextType.ERROR) {
const provideReferenceResult = await this.provideContext(context);
if (!isSuccessCommandResult(provideReferenceResult)) {
return Just(provideReferenceResult);
}
}
}
return Nothing;
}

/**
* This method will send a command according to the clear sign context type
* and return the command result if only one command is sent,
Expand All @@ -130,20 +181,15 @@ export class ProvideTransactionGenericContextTask {
>
> {
switch (type) {
case ClearSignContextType.PLUGIN: {
return await this.api.sendCommand(new SetPluginCommand({ payload }));
}
case ClearSignContextType.NFT: {
case ClearSignContextType.NFT:
return await this.api.sendCommand(
new ProvideNFTInformationCommand({ payload }),
);
}
case ClearSignContextType.TOKEN: {
case ClearSignContextType.TOKEN:
return await this.api.sendCommand(
new ProvideTokenInformationCommand({ payload }),
);
}
case ClearSignContextType.TRUSTED_NAME: {
case ClearSignContextType.TRUSTED_NAME:
return this.sendInChunks(
payload,
(args) =>
Expand All @@ -152,8 +198,7 @@ export class ProvideTransactionGenericContextTask {
isFirstChunk: args.isFirstChunk,
}),
);
}
case ClearSignContextType.ENUM: {
case ClearSignContextType.ENUM:
return this.sendInChunks(
payload,
(args) =>
Expand All @@ -162,8 +207,7 @@ export class ProvideTransactionGenericContextTask {
isFirstChunk: args.isFirstChunk,
}),
);
}
case ClearSignContextType.TRANSACTION_FIELD_DESCRIPTION: {
case ClearSignContextType.TRANSACTION_FIELD_DESCRIPTION:
return this.sendInChunks(
payload,
(args) =>
Expand All @@ -172,24 +216,14 @@ export class ProvideTransactionGenericContextTask {
isFirstChunk: args.isFirstChunk,
}),
);
}
case ClearSignContextType.TRANSACTION_INFO: {
return this.sendInChunks(
payload,
(args) =>
new ProvideTransactionInformationCommand({
data: args.chunkedData,
isFirstChunk: args.isFirstChunk,
}),
);
}
case ClearSignContextType.EXTERNAL_PLUGIN: {
case ClearSignContextType.TRANSACTION_INFO:
case ClearSignContextType.PLUGIN:
case ClearSignContextType.EXTERNAL_PLUGIN:
return CommandResultFactory({
error: new InvalidStatusWordError(
"The context type [EXTERNAL_PLUGIN] is not valid here",
`The context type [${type}] is not valid as a transaction field or metadata`,
),
});
}
default: {
const uncoveredType: never = type;
return CommandResultFactory({
Expand Down

0 comments on commit ba98481

Please sign in to comment.