@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
100 lines (82 loc) • 3.15 kB
text/typescript
import { lastValueFrom } from "rxjs";
import { toArray } from "rxjs/operators";
import { genericSignOperation } from "../signOperation";
import { FeeNotLoaded } from "@ledgerhq/errors";
import { getAlpacaApi } from "../alpaca";
import { buildOptimisticOperation, transactionToIntent } from "../utils";
jest.mock("../alpaca", () => ({
getAlpacaApi: jest.fn(),
}));
jest.mock("../utils", () => ({
...jest.requireActual("../utils"),
buildOptimisticOperation: jest.fn(),
transactionToIntent: jest.fn(),
}));
describe("genericSignOperation", () => {
const mockSignerContext = jest.fn();
const mockSigner = {
getAddress: jest.fn(),
signTransaction: jest.fn(),
};
const transaction = {
amount: 100_000n,
fees: 500n,
tag: 1234,
recipientDomain: {
domain: "recipient.gen",
address: "recipient-address",
},
} as any;
const txIntent = {
memo: {
type: "map",
memos: new Map(),
},
};
beforeEach(() => {
jest.clearAllMocks();
(getAlpacaApi as jest.Mock).mockReturnValue({
craftTransaction: jest.fn().mockResolvedValue({ transaction: "unsignedTx" }),
getAccountInfo: jest.fn().mockResolvedValue("pubKey"),
combine: jest.fn().mockResolvedValue("signedTx"),
getNextSequence: jest.fn().mockResolvedValue(1n),
});
(transactionToIntent as jest.Mock).mockReturnValue(txIntent);
(buildOptimisticOperation as jest.Mock).mockReturnValue({ id: "mock-op" });
mockSigner.getAddress.mockResolvedValue({ publicKey: "pubKey" });
mockSigner.signTransaction.mockResolvedValue("sig");
mockSignerContext.mockImplementation(async (_deviceId, cb) => cb(mockSigner));
});
const account = {
freshAddressPath: "44'/144'/0'/0/0",
address: "rTestAddress",
currency: { id: "testnet" },
} as any;
it("emits full sign operation flow", async () => {
const signOperation = genericSignOperation("testnet", "local")(mockSignerContext);
const observable = signOperation({ account, transaction, deviceId: "" });
const events = await lastValueFrom(observable.pipe(toArray()));
expect(events[0]).toEqual({ type: "device-signature-requested" });
expect(events[1]).toEqual({ type: "device-signature-granted" });
expect(events[2]).toEqual({
type: "signed",
signedOperation: {
operation: { id: "mock-op" },
signature: "signedTx",
},
});
expect(transactionToIntent).toHaveBeenCalledWith(account, transaction, undefined);
expect(mockSigner.signTransaction).toHaveBeenCalledWith("44'/144'/0'/0/0", "unsignedTx", {
domain: "recipient.gen",
address: "recipient-address",
});
expect(txIntent.memo.memos.get("destinationTag")).toBe("1234");
});
it("throws FeeNotLoaded if fees are missing", async () => {
const txWithoutFees = { ...transaction };
delete txWithoutFees.fees;
const signOperation = genericSignOperation("testnet", "local")(mockSignerContext);
const observable = signOperation({ account, transaction: txWithoutFees, deviceId: "" });
await expect(lastValueFrom(observable)).rejects.toThrow(FeeNotLoaded);
});
});