@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
198 lines (182 loc) • 6.55 kB
text/typescript
import expect from "expect";
import { Transaction } from "../models/Transaction";
import { pressUntilTextFound, containsSubstringInEvent, waitFor } from "../speculos";
import { getSpeculosModel, isTouchDevice } from "../speculosAppVersion";
import {
pressAndRelease,
longPressAndRelease,
swipeRight,
} from "../deviceInteraction/TouchDeviceSimulator";
import { DeviceLabels } from "../enum/DeviceLabels";
import { DeviceModelId } from "@ledgerhq/types-devices";
import { withDeviceController } from "../deviceInteraction/DeviceController";
type ActionType = "both" | "right" | "tap" | "swipe" | "confirm" | "hold";
function validateTransactionData(tx: Transaction, events: string[]) {
if (!tx.accountToCredit.address) {
throw new Error("Recipient address is not set");
}
const isAddressCorrect = containsSubstringInEvent(tx.accountToCredit.address, events);
expect(isAddressCorrect).toBeTruthy();
const isAmountCorrect = containsSubstringInEvent(tx.amount, events);
expect(isAmountCorrect).toBeTruthy();
}
async function sendCardanoTouchDevices(tx: Transaction) {
await waitFor(DeviceLabels.REVIEW_TRANSACTION);
const events = await pressUntilTextFound(DeviceLabels.AMOUNT);
validateTransactionData(tx, events);
await pressAndRelease(DeviceLabels.TAP_TO_CONTINUE);
await waitFor(DeviceLabels.FEES);
await pressAndRelease(DeviceLabels.TAP_TO_CONTINUE);
await waitFor(DeviceLabels.SIGN_TRANSACTION);
await longPressAndRelease(DeviceLabels.HOLD_TO_SIGN, 3);
}
export const sendCardanoNanoS = withDeviceController(
({ getButtonsController }) =>
async (_tx: Transaction) => {
const buttons = getButtonsController();
await waitFor(DeviceLabels.NEW_ORDINARY);
await buttons.right();
await waitFor(DeviceLabels.SEND_TO_ADDRESS);
await buttons.both();
await pressUntilTextFound(DeviceLabels.SEND);
await buttons.both();
await waitFor(DeviceLabels.TRANSACTION_FEE);
await buttons.both();
await waitFor(DeviceLabels.CONFIRM);
await buttons.right();
},
);
export const sendCardanoButtonDevice = withDeviceController(
({ getButtonsController }) =>
async (tx: Transaction) => {
const buttons = getButtonsController();
await waitFor(DeviceLabels.NEW_ORDINARY);
await buttons.both();
await pressUntilTextFound(DeviceLabels.SEND_TO_ADDRESS_2);
await buttons.both();
const events = await pressUntilTextFound(DeviceLabels.SEND);
validateTransactionData(tx, events);
await buttons.both();
await waitFor(DeviceLabels.TRANSACTION_FEE);
await buttons.both();
await waitFor(DeviceLabels.CONFIRM);
await buttons.both();
},
);
export async function sendCardano(tx: Transaction) {
const speculosModel = getSpeculosModel();
if (isTouchDevice()) {
return sendCardanoTouchDevices(tx);
}
if (speculosModel === DeviceModelId.nanoS) {
return sendCardanoNanoS(tx);
}
return sendCardanoButtonDevice(tx);
}
const TOUCH_DELEGATE_STEPS = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
] as const;
const DELEGATE_STEPS_CONFIG = {
[]: TOUCH_DELEGATE_STEPS,
[]: TOUCH_DELEGATE_STEPS,
[]: TOUCH_DELEGATE_STEPS,
[]: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
] as const,
default: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
] as const,
};
function getConfirmButtonCoords(): { x: number; y: number } {
const speculosModel = getSpeculosModel();
switch (speculosModel) {
case DeviceModelId.stax:
return { x: 152, y: 532 };
case DeviceModelId.apex:
return { x: 114, y: 305 };
case DeviceModelId.europa:
default:
return { x: 186, y: 446 };
}
}
async function delegateTouchDevicesAction(label: DeviceLabels) {
const CONFIRM_BUTTON_COORDS = getConfirmButtonCoords();
await waitFor(label);
switch (label) {
case DeviceLabels.TAP_TO_CONTINUE:
await pressAndRelease(DeviceLabels.TAP_TO_CONTINUE);
break;
case DeviceLabels.CONFIRM:
await pressAndRelease(DeviceLabels.CONFIRM, CONFIRM_BUTTON_COORDS.x, CONFIRM_BUTTON_COORDS.y);
break;
case DeviceLabels.HOLD_TO_SIGN:
await longPressAndRelease(DeviceLabels.HOLD_TO_SIGN, 3);
break;
default:
await swipeRight();
break;
}
}
export const delegateNanoAction = withDeviceController(
({ getButtonsController }) =>
async (label: DeviceLabels, action: ActionType) => {
const buttons = getButtonsController();
await waitFor(label);
if (action === "both") {
await buttons.both();
} else {
await buttons.right();
}
},
);
async function executeDelegateStep(label: DeviceLabels, action: ActionType) {
try {
if (isTouchDevice()) {
await delegateTouchDevicesAction(label);
} else {
await delegateNanoAction(label, action);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Error while waiting for "${label}":`, message);
throw error;
}
}
export async function delegateCardano() {
const speculosModel = getSpeculosModel();
const steps =
isTouchDevice() && DELEGATE_STEPS_CONFIG[speculosModel]
? DELEGATE_STEPS_CONFIG[speculosModel]
: speculosModel === DeviceModelId.nanoS
? DELEGATE_STEPS_CONFIG[DeviceModelId.nanoS]
: DELEGATE_STEPS_CONFIG.default;
for (const [label, action] of steps) {
await executeDelegateStep(label, action);
}
}