@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
631 lines • 25.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const currencies_1 = require("../currencies");
const signMessage = __importStar(require("../hw/signMessage/index"));
const cryptoCurrencies_1 = require("../mock/fixtures/cryptoCurrencies");
const converters = __importStar(require("./converters"));
const logic_1 = require("./logic");
const serializers = __importStar(require("./serializers"));
const store_1 = require("@ledgerhq/live-wallet/store");
describe("receiveOnAccountLogic", () => {
const walletState = store_1.initialState;
// Given
const mockPlatformReceiveRequested = jest.fn();
const mockPlatformReceiveFail = jest.fn();
const context = createContextContainingAccountId({
platformReceiveRequested: mockPlatformReceiveRequested,
platformReceiveFail: mockPlatformReceiveFail,
}, "11", "12");
const uiNavigation = jest.fn();
beforeEach(() => {
mockPlatformReceiveRequested.mockClear();
mockPlatformReceiveFail.mockClear();
uiNavigation.mockClear();
});
describe("when nominal case", () => {
// Given
const accountId = "js:2:ethereum:0x012:";
const expectedResult = "Function called";
beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult));
it("calls uiNavigation callback with an accountAddress", async () => {
// Given
const convertedAccount = {
...createPlatformAccount(),
address: "Converted address",
};
jest.spyOn(converters, "accountToPlatformAccount").mockReturnValueOnce(convertedAccount);
// When
const result = await (0, logic_1.receiveOnAccountLogic)(walletState, context, accountId, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][2]).toEqual("Converted address");
expect(result).toEqual(expectedResult);
});
it("calls the tracking for success", async () => {
// When
await (0, logic_1.receiveOnAccountLogic)(walletState, context, accountId, uiNavigation);
// Then
expect(mockPlatformReceiveRequested).toBeCalledTimes(1);
expect(mockPlatformReceiveFail).toBeCalledTimes(0);
});
});
describe("when account cannot be found", () => {
// Given
const nonFoundAccountId = "js:2:ethereum:0x010:";
it("returns an error", async () => {
// When
await expect(async () => {
await (0, logic_1.receiveOnAccountLogic)(walletState, context, nonFoundAccountId, uiNavigation);
}).rejects.toThrowError("Account required");
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
it("calls the tracking for error", async () => {
// When
await expect(async () => {
await (0, logic_1.receiveOnAccountLogic)(walletState, context, nonFoundAccountId, uiNavigation);
}).rejects.toThrow();
// Then
expect(mockPlatformReceiveRequested).toBeCalledTimes(1);
expect(mockPlatformReceiveFail).toBeCalledTimes(1);
});
});
});
describe("completeExchangeLogic", () => {
// Given
const mockPlatformCompleteExchangeRequested = jest.fn();
const context = createContextContainingAccountId({
platformCompleteExchangeRequested: mockPlatformCompleteExchangeRequested,
}, "11", "12");
const uiNavigation = jest.fn();
beforeAll(() => {
(0, currencies_1.setSupportedCurrencies)(["bitcoin", "ethereum"]);
});
afterAll(() => {
(0, currencies_1.setSupportedCurrencies)([]);
});
beforeEach(() => {
mockPlatformCompleteExchangeRequested.mockClear();
uiNavigation.mockClear();
});
describe("when nominal case", () => {
// Given
const expectedResult = "Function called";
beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult));
it("calls uiNavigation callback (token)", async () => {
// Given
const fromAccount = (0, cryptoCurrencies_1.createFixtureTokenAccount)("16");
const fromParentAccount = (0, cryptoCurrencies_1.createFixtureAccount)("16");
context.accounts = [...context.accounts, fromAccount, fromParentAccount];
const rawPlatformTransaction = createRawEtherumTransaction();
const completeExchangeRequest = {
provider: "provider",
fromAccountId: "js:2:ethereum:0x16:+ethereum%2Ferc20%2Fusd_tether__erc20_",
toAccountId: "js:2:ethereum:0x042:",
transaction: rawPlatformTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "medium",
exchangeType: 8,
};
const expectedTransaction = {
family: "evm",
amount: new bignumber_js_1.default("1000000000"),
subAccountId: "js:2:ethereum:0x16:+ethereum%2Ferc20%2Fusd_tether__erc20_",
recipient: "0x0123456",
nonce: 8,
data: Buffer.from("Some data...", "hex"),
type: 0,
gasPrice: new bignumber_js_1.default("700000"),
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
gasLimit: new bignumber_js_1.default("1200000"),
customGasLimit: new bignumber_js_1.default("1200000"),
feesStrategy: "medium",
mode: "send",
useAllAmount: false,
chainId: 1,
};
// When
const result = await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][0]).toEqual({
provider: "provider",
exchange: {
fromAccount,
fromParentAccount,
fromCurrency: fromAccount.token,
toAccount: undefined,
toParentAccount: undefined,
toCurrency: undefined,
},
transaction: expectedTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "medium",
exchangeType: 8,
});
expect(result).toEqual(expectedResult);
});
it("calls uiNavigation callback (coin)", async () => {
// Given
const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17");
context.accounts = [...context.accounts, fromAccount];
const rawPlatformTransaction = createRawEtherumTransaction();
const completeExchangeRequest = {
provider: "provider",
fromAccountId: "js:2:ethereum:0x017:",
toAccountId: "js:2:ethereum:0x042:",
transaction: rawPlatformTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "medium",
exchangeType: 8,
};
const expectedTransaction = {
family: "evm",
amount: new bignumber_js_1.default("1000000000"),
recipient: "0x0123456",
nonce: 8,
data: Buffer.from("Some data...", "hex"),
gasPrice: new bignumber_js_1.default("700000"),
gasLimit: new bignumber_js_1.default("1200000"),
customGasLimit: new bignumber_js_1.default("1200000"),
feesStrategy: "medium",
mode: "send",
useAllAmount: false,
chainId: 1,
subAccountId: undefined,
type: 0,
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
};
// When
const result = await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][0]).toEqual({
provider: "provider",
exchange: {
fromAccount,
fromParentAccount: undefined,
fromCurrency: fromAccount.currency,
toAccount: undefined,
toParentAccount: undefined,
toCurrency: undefined,
},
transaction: expectedTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "medium",
exchangeType: 8,
});
expect(result).toEqual(expectedResult);
});
it.each(["slow", "medium", "fast", "custom"])("calls uiNavigation with a transaction that has the %s feeStrategy", async (expectedFeeStrategy) => {
// Given
const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17");
context.accounts = [...context.accounts, fromAccount];
const rawPlatformTransaction = createRawEtherumTransaction();
const completeExchangeRequest = {
provider: "provider",
fromAccountId: "js:2:ethereum:0x017:",
toAccountId: "js:2:ethereum:0x042:",
transaction: rawPlatformTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: expectedFeeStrategy,
exchangeType: 8,
};
// When
await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][0]["transaction"].feesStrategy).toEqual(expectedFeeStrategy);
});
it("calls the tracking for success", async () => {
// Given
const completeExchangeRequest = {
provider: "provider",
fromAccountId: "js:2:ethereum:0x012:",
toAccountId: "js:2:ethereum:0x042:",
transaction: createRawEtherumTransaction(),
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "feeStrategy",
exchangeType: 8,
};
// When
await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation);
// Then
expect(mockPlatformCompleteExchangeRequested).toBeCalledTimes(1);
});
});
describe("when Account is from a different family than the transaction", () => {
// Given
const expectedResult = "Function called";
beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult));
it("returns an error", async () => {
// Given
const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17");
context.accounts = [...context.accounts, fromAccount];
const rawPlatformTransaction = createRawBitcoinTransaction();
const completeExchangeRequest = {
provider: "provider",
fromAccountId: "js:2:ethereum:0x017:",
toAccountId: "js:2:ethereum:0x042:",
transaction: rawPlatformTransaction,
binaryPayload: "binaryPayload",
signature: "signature",
feesStrategy: "feeStrategy",
exchangeType: 8,
};
// When
await expect(async () => {
await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation);
}).rejects.toThrowError("Account and transaction must be from the same family");
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
});
});
describe("broadcastTransactionLogic", () => {
// Given
const mockplatformBroadcastFail = jest.fn();
const context = createContextContainingAccountId({
platformBroadcastFail: mockplatformBroadcastFail,
}, "11", "12");
const uiNavigation = jest.fn();
beforeEach(() => {
mockplatformBroadcastFail.mockClear();
uiNavigation.mockClear();
});
describe("when nominal case", () => {
// Given
const accountId = "js:2:ethereum:0x012:";
const rawSignedTransaction = createSignedOperationRaw();
it("calls uiNavigation callback with a signedOperation", async () => {
// Given
const expectedResult = "Function called";
const signedOperation = createSignedOperation();
jest
.spyOn(serializers, "deserializePlatformSignedTransaction")
.mockReturnValueOnce(signedOperation);
uiNavigation.mockResolvedValueOnce(expectedResult);
// When
const result = await (0, logic_1.broadcastTransactionLogic)(context, accountId, rawSignedTransaction, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][2]).toEqual(signedOperation);
expect(result).toEqual(expectedResult);
});
it("calls the tracking for success", async () => {
// When
await (0, logic_1.broadcastTransactionLogic)(context, accountId, rawSignedTransaction, uiNavigation);
// Then
expect(mockplatformBroadcastFail).toBeCalledTimes(0);
});
});
describe("when account cannot be found", () => {
// Given
const nonFoundAccountId = "js:2:ethereum:0x010:";
const rawSignedTransaction = createSignedOperationRaw();
it("returns an error", async () => {
// Given
const expectedResult = "Function called";
const signedOperation = createSignedOperation();
jest
.spyOn(serializers, "deserializePlatformSignedTransaction")
.mockReturnValueOnce(signedOperation);
uiNavigation.mockResolvedValueOnce(expectedResult);
// When
await expect(async () => {
await (0, logic_1.broadcastTransactionLogic)(context, nonFoundAccountId, rawSignedTransaction, uiNavigation);
}).rejects.toThrowError("Account required");
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
it("calls the tracking for error", async () => {
// When
await expect(async () => {
await (0, logic_1.broadcastTransactionLogic)(context, nonFoundAccountId, rawSignedTransaction, uiNavigation);
}).rejects.toThrow();
// Then
expect(mockplatformBroadcastFail).toBeCalledTimes(1);
});
});
});
describe("signMessageLogic", () => {
// Given
const mockPlatformSignMessageRequested = jest.fn();
const mockPlatformSignMessageFail = jest.fn();
const context = createContextContainingAccountId({
platformSignMessageRequested: mockPlatformSignMessageRequested,
platformSignMessageFail: mockPlatformSignMessageFail,
}, "11", "12");
const uiNavigation = jest.fn();
beforeEach(() => {
mockPlatformSignMessageRequested.mockClear();
mockPlatformSignMessageFail.mockClear();
uiNavigation.mockClear();
});
describe("when nominal case", () => {
// Given
const accountId = "js:2:ethereum:0x012:";
const messageToSign = "Message to sign";
const spyPrepareMessageToSign = jest.spyOn(signMessage, "prepareMessageToSign");
beforeEach(() => spyPrepareMessageToSign.mockClear());
it("calls uiNavigation callback with a signedOperation", async () => {
// Given
const expectedResult = "Function called";
const formattedMessage = createMessageData();
spyPrepareMessageToSign.mockReturnValueOnce(formattedMessage);
uiNavigation.mockResolvedValueOnce(expectedResult);
// When
const result = await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation);
// Then
expect(uiNavigation).toBeCalledTimes(1);
expect(uiNavigation.mock.calls[0][1]).toEqual(formattedMessage);
expect(result).toEqual(expectedResult);
});
it("calls the tracking for success", async () => {
// When
await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation);
// Then
expect(mockPlatformSignMessageRequested).toBeCalledTimes(1);
expect(mockPlatformSignMessageFail).toBeCalledTimes(0);
});
});
describe("when account cannot be found", () => {
// Given
const nonFoundAccountId = "js:2:ethereum:0x010:";
const messageToSign = "Message to sign";
it("returns an error", async () => {
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, nonFoundAccountId, messageToSign, uiNavigation);
}).rejects.toThrowError(`account with id "${nonFoundAccountId}" not found`);
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
it("calls the tracking for error", async () => {
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, nonFoundAccountId, messageToSign, uiNavigation);
}).rejects.toThrow();
// Then
expect(mockPlatformSignMessageRequested).toBeCalledTimes(1);
expect(mockPlatformSignMessageFail).toBeCalledTimes(1);
});
});
describe("when account found is not of type 'Account'", () => {
// Given
const tokenAccountId = "15";
const messageToSign = "Message to sign";
context.accounts = [createTokenAccount(tokenAccountId), ...context.accounts];
it("returns an error", async () => {
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, tokenAccountId, messageToSign, uiNavigation);
}).rejects.toThrowError("account provided should be the main one");
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
it("calls the tracking for error", async () => {
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, tokenAccountId, messageToSign, uiNavigation);
}).rejects.toThrow();
// Then
expect(mockPlatformSignMessageRequested).toBeCalledTimes(1);
expect(mockPlatformSignMessageFail).toBeCalledTimes(1);
});
});
describe("when inner call prepareMessageToSign raise an error", () => {
// Given
const accountId = "js:2:ethereum:0x012:";
const messageToSign = "Message to sign";
const spyPrepareMessageToSign = jest.spyOn(signMessage, "prepareMessageToSign");
beforeEach(() => spyPrepareMessageToSign.mockClear());
it("returns an error", async () => {
// Given
spyPrepareMessageToSign.mockImplementationOnce(() => {
throw new Error("Some error");
});
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation);
}).rejects.toThrowError("Some error");
// Then
expect(uiNavigation).toBeCalledTimes(0);
});
it("calls the tracking for error", async () => {
// Given
spyPrepareMessageToSign.mockImplementationOnce(() => {
throw new Error("Some error");
});
// When
await expect(async () => {
await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation);
}).rejects.toThrow();
// Then
expect(mockPlatformSignMessageRequested).toBeCalledTimes(1);
expect(mockPlatformSignMessageFail).toBeCalledTimes(1);
});
});
});
function createAppManifest(id = "1") {
return {
id,
private: false,
name: "New App Manifest",
url: "https://www.ledger.com",
homepageUrl: "https://www.ledger.com",
supportUrl: "https://www.ledger.com",
icon: null,
platforms: ["ios", "android", "desktop"],
apiVersion: "1.0.0",
manifestVersion: "1.0.0",
branch: "debug",
params: undefined,
categories: [],
currencies: "*",
content: {
shortDescription: {
en: "short description",
},
description: {
en: "description",
},
},
permissions: [],
domains: [],
visibility: "complete",
};
}
function createContextContainingAccountId(tracking, ...accountIds) {
return {
manifest: createAppManifest(),
accounts: [...accountIds.map(val => (0, cryptoCurrencies_1.createFixtureAccount)(val)), (0, cryptoCurrencies_1.createFixtureAccount)()],
tracking: tracking,
};
}
function createSignedOperation() {
const operation = {
id: "42",
hash: "hashed",
type: "IN",
value: new bignumber_js_1.default(0),
fee: new bignumber_js_1.default(0),
senders: [],
recipients: [],
blockHeight: null,
blockHash: null,
accountId: "14",
date: new Date(),
extra: {},
};
return {
operation,
signature: "Signature",
};
}
function createSignedOperationRaw() {
const rawOperation = {
id: "12",
hash: "123456",
type: "CREATE",
value: "0",
fee: "0",
senders: [],
recipients: [],
blockHeight: null,
blockHash: null,
accountId: "12",
date: "01/01/1970",
extra: {},
};
return {
operation: rawOperation,
signature: "Signature",
};
}
function createPlatformAccount() {
return {
id: "12",
name: "",
address: "",
currency: "",
balance: new bignumber_js_1.default(0),
spendableBalance: new bignumber_js_1.default(0),
blockHeight: 0,
lastSyncDate: new Date(),
};
}
function createMessageData() {
return {
account: (0, cryptoCurrencies_1.createFixtureAccount)("17"),
message: "default message",
};
}
function createTokenAccount(id = "32") {
return {
type: "TokenAccount",
id,
parentId: "whatever",
token: createTokenCurrency(),
balance: new bignumber_js_1.default(0),
spendableBalance: new bignumber_js_1.default(0),
creationDate: new Date(),
operationsCount: 0,
operations: [],
pendingOperations: [],
balanceHistoryCache: {
WEEK: { latestDate: null, balances: [] },
HOUR: { latestDate: null, balances: [] },
DAY: { latestDate: null, balances: [] },
},
swapHistory: [],
};
}
function createTokenCurrency() {
return {
type: "TokenCurrency",
id: "3",
contractAddress: "",
parentCurrency: (0, cryptoCurrencies_1.createFixtureCryptoCurrency)("eth"),
tokenType: "",
//-- CurrencyCommon
name: "",
ticker: "",
units: [],
};
}
function createRawEtherumTransaction() {
return {
family: "ethereum",
amount: "1000000000",
recipient: "0x0123456",
nonce: 8,
data: "Some data...",
gasPrice: "700000",
gasLimit: "1200000",
};
}
function createRawBitcoinTransaction() {
return {
family: "bitcoin",
amount: "1000000000",
recipient: "0x0123456",
feePerByte: "900000",
};
}
//# sourceMappingURL=logic.test.js.map