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 = [ [DeviceLabels.REVIEW_TRANSACTION, "swipe"], [DeviceLabels.TAP_TO_CONTINUE, "tap"], [DeviceLabels.REGISTER, "swipe"], [DeviceLabels.TAP_TO_CONTINUE, "tap"], [DeviceLabels.CONFIRM, "confirm"], [DeviceLabels.DELEGATE_STAKE, "swipe"], [DeviceLabels.TAP_TO_CONTINUE, "tap"], [DeviceLabels.CONFIRM, "confirm"], [DeviceLabels.HOLD_TO_SIGN, "hold"], ] as const; const DELEGATE_STEPS_CONFIG = { [DeviceModelId.stax]: TOUCH_DELEGATE_STEPS, [DeviceModelId.europa]: TOUCH_DELEGATE_STEPS, [DeviceModelId.apex]: TOUCH_DELEGATE_STEPS, [DeviceModelId.nanoS]: [ [DeviceLabels.NEW_ORDINARY, "right"], [DeviceLabels.TRANSACTION_FEE, "both"], [DeviceLabels.REGISTER, "both"], [DeviceLabels.STAKE_KEY, "both"], [DeviceLabels.DEPOSIT, "both"], [DeviceLabels.CONFIRM, "right"], [DeviceLabels.DELEGATE_STAKE, "both"], [DeviceLabels.STAKE_KEY, "both"], [DeviceLabels.CONFIRM, "right"], [DeviceLabels.CONFIRM, "right"], ] as const, default: [ [DeviceLabels.NEW_ORDINARY, "both"], [DeviceLabels.TRANSACTION_FEE, "both"], [DeviceLabels.REGISTER, "both"], [DeviceLabels.STAKE_KEY, "both"], [DeviceLabels.DEPOSIT, "both"], [DeviceLabels.CONFIRM, "both"], [DeviceLabels.DELEGATE_STAKE, "both"], [DeviceLabels.STAKE_KEY, "both"], [DeviceLabels.CONFIRM, "both"], [DeviceLabels.CONFIRM, "both"], ] 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); } }