Skip to content

Commit

Permalink
✨ (signer-btc): Create Prepare Wallet Policy task
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabbech-ledger committed Dec 17, 2024
1 parent 633ed6f commit 811ca20
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-wombats-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-bitcoin": minor
---

PrepareWalletPolicy task
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("GetMasterFingerprintCommand", () => {
expect(result).toEqual(
CommandResultFactory({
data: {
masterFingerprint: "828dc2f3",
masterFingerprint: Uint8Array.from([0x82, 0x8d, 0xc2, 0xf3]),
},
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
const MASTER_FINGERPRINT_LENGTH = 4;

type GetMasterFingerprintCommandResponse = {
masterFingerprint: string;
masterFingerprint: Uint8Array;
};

export class GetMasterFingerprintCommand
Expand All @@ -43,16 +43,16 @@ export class GetMasterFingerprintCommand
});
}

if (!parser.testMinimalLength(MASTER_FINGERPRINT_LENGTH)) {
const masterFingerprint = parser.extractFieldByLength(
MASTER_FINGERPRINT_LENGTH,
);

if (!masterFingerprint) {
return CommandResultFactory({
error: new InvalidStatusWordError("Master fingerprint is missing"),
});
}

const masterFingerprint = parser.encodeToHexaString(
parser.extractFieldByLength(MASTER_FINGERPRINT_LENGTH),
);

return CommandResultFactory({
data: {
masterFingerprint,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
CommandResultFactory,
type InternalApi,
isSuccessCommandResult,
UnknownDeviceExchangeError,
} from "@ledgerhq/device-management-kit";

import {
DefaultDescriptorTemplate,
DefaultWallet,
type Wallet,
} from "@api/model/Wallet";
import { PrepareWalletPolicyTask } from "@internal/app-binder/task/PrepareWalletPolicyTask";
import { DataStore } from "@internal/data-store/model/DataStore";
import { type DataStoreService } from "@internal/data-store/service/DataStoreService";
import { type WalletBuilder } from "@internal/wallet/service/WalletBuilder";
const fromDefaultWalletMock = jest.fn();
const merklizeWalletMock = jest.fn();

describe("PrepareWalletPolicyTask", () => {
let internalApi: { sendCommand: jest.Mock };
const walletBuilder = {
fromDefaultWallet: fromDefaultWalletMock,
} as unknown as WalletBuilder;
const dataStoreService = {
merklizeWallet: merklizeWalletMock,
} as unknown as DataStoreService;
beforeEach(() => {
internalApi = {
sendCommand: jest.fn(),
};
});
afterEach(() => {
jest.resetAllMocks();
});

it("should return a filled data store", async () => {
// given
const defaultWallet = new DefaultWallet(
"49'/0'/0'",
DefaultDescriptorTemplate.LEGACY,
);
const task = new PrepareWalletPolicyTask(
internalApi as unknown as InternalApi,
walletBuilder,
dataStoreService,
);
internalApi.sendCommand.mockResolvedValueOnce(
Promise.resolve(
CommandResultFactory({
data: {
masterFingerprint: Uint8Array.from([0x42, 0x21, 0x12, 0x24]),
},
}),
),
);
internalApi.sendCommand.mockResolvedValueOnce(
Promise.resolve(
CommandResultFactory({
data: {
extendedPublicKey: "xPublicKey",
},
}),
),
);
const wallet = {} as Wallet;
fromDefaultWalletMock.mockReturnValue(wallet);
// when
const result = await task.run(defaultWallet);
// then
if (isSuccessCommandResult(result)) {
expect(fromDefaultWalletMock).toHaveBeenCalledWith(
Uint8Array.from([0x42, 0x21, 0x12, 0x24]),
"xPublicKey",
defaultWallet,
);
expect(merklizeWalletMock).toHaveBeenCalledWith(new DataStore(), wallet);
expect(result.data).toBeInstanceOf(DataStore);
} else {
fail("Expected a success result, but the result was an error");
}
});

it("should return an error if getMasterFingerprint failed", async () => {
// given
const defaultWallet = new DefaultWallet(
"49'/0'/0'",
DefaultDescriptorTemplate.LEGACY,
);
const task = new PrepareWalletPolicyTask(
internalApi as unknown as InternalApi,
walletBuilder,
dataStoreService,
);
const error = new UnknownDeviceExchangeError("Failed");
internalApi.sendCommand.mockResolvedValueOnce(
Promise.resolve(
CommandResultFactory({
error,
}),
),
);
// when
const result = await task.run(defaultWallet);
// then
if (isSuccessCommandResult(result) === false) {
expect(result.error).toStrictEqual(error);
} else {
fail("Expected an error, but the result was successful");
}
});

it("should return an error if getExtendedPublicKey failed", async () => {
// given
const defaultWallet = new DefaultWallet(
"49'/0'/0'",
DefaultDescriptorTemplate.LEGACY,
);
const task = new PrepareWalletPolicyTask(
internalApi as unknown as InternalApi,
walletBuilder,
dataStoreService,
);
const error = new UnknownDeviceExchangeError("Failed");
internalApi.sendCommand.mockResolvedValueOnce(
Promise.resolve(
CommandResultFactory({
data: {
masterFingerprint: Uint8Array.from([0x42, 0x21, 0x12, 0x24]),
},
}),
),
);
internalApi.sendCommand.mockResolvedValueOnce(
Promise.resolve(
CommandResultFactory({
error,
}),
),
);
// when
const result = await task.run(defaultWallet);
// then
if (isSuccessCommandResult(result) === false) {
expect(result.error).toStrictEqual(error);
} else {
fail("Expected an error, but the result was successful");
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
type CommandResult,
CommandResultFactory,
type InternalApi,
isSuccessCommandResult,
} from "@ledgerhq/device-management-kit";

import { type DefaultWallet } from "@api/model/Wallet";
import { GetExtendedPublicKeyCommand } from "@internal/app-binder/command/GetExtendedPublicKeyCommand";
import { GetMasterFingerprintCommand } from "@internal/app-binder/command/GetMasterFingerprintCommand";
import { DataStore } from "@internal/data-store/model/DataStore";
import { type DataStoreService } from "@internal/data-store/service/DataStoreService";
import { type WalletBuilder } from "@internal/wallet/service/WalletBuilder";

export class PrepareWalletPolicyTask {
constructor(
private readonly _api: InternalApi,
private readonly _walletBuilder: WalletBuilder,
private readonly _dataStoreService: DataStoreService,
) {}

async run(defaultWallet: DefaultWallet): Promise<CommandResult<DataStore>> {
// request master fingerprint
const getMasterFingerPrintResult = await this._api.sendCommand(
new GetMasterFingerprintCommand(),
);
if (!isSuccessCommandResult(getMasterFingerPrintResult)) {
return getMasterFingerPrintResult;
}

// request extended public key for derivation path
const getExtendedPublicKeyResult = await this._api.sendCommand(
new GetExtendedPublicKeyCommand({
derivationPath: defaultWallet.derivationPath,
checkOnDevice: false,
}),
);
if (!isSuccessCommandResult(getExtendedPublicKeyResult)) {
return getExtendedPublicKeyResult;
}
// create default wallet with wallet policy service
const wallet = this._walletBuilder.fromDefaultWallet(
getMasterFingerPrintResult.data.masterFingerprint,
getExtendedPublicKeyResult.data.extendedPublicKey,
defaultWallet,
);
// feed the data store
const store = new DataStore();
this._dataStoreService.merklizeWallet(store, wallet);

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

0 comments on commit 811ca20

Please sign in to comment.