@ledgerhq/coin-tester 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +20 -0
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE.txt +21 -0
- package/README.md +43 -0
- package/docker-compose.yml +15 -0
- package/jest.config.js +9 -0
- package/lib/main.d.ts +30 -0
- package/lib/main.d.ts.map +1 -0
- package/lib/main.js +121 -0
- package/lib/main.js.map +1 -0
- package/lib/signers/speculos.d.ts +8 -0
- package/lib/signers/speculos.d.ts.map +1 -0
- package/lib/signers/speculos.js +89 -0
- package/lib/signers/speculos.js.map +1 -0
- package/lib/types.d.ts +6 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +3 -0
- package/lib/types.js.map +1 -0
- package/lib-es/main.d.ts +30 -0
- package/lib-es/main.d.ts.map +1 -0
- package/lib-es/main.js +114 -0
- package/lib-es/main.js.map +1 -0
- package/lib-es/signers/speculos.d.ts +8 -0
- package/lib-es/signers/speculos.d.ts.map +1 -0
- package/lib-es/signers/speculos.js +81 -0
- package/lib-es/signers/speculos.js.map +1 -0
- package/lib-es/types.d.ts +6 -0
- package/lib-es/types.d.ts.map +1 -0
- package/lib-es/types.js +2 -0
- package/lib-es/types.js.map +1 -0
- package/package.json +68 -0
- package/src/main.ts +239 -0
- package/src/signers/speculos.ts +95 -0
- package/src/types.ts +5 -0
- package/tsconfig.json +15 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
browser: true,
|
|
4
|
+
es6: true,
|
|
5
|
+
},
|
|
6
|
+
ignorePatterns: ["lib", "lib-es", ".turbo"],
|
|
7
|
+
overrides: [
|
|
8
|
+
{
|
|
9
|
+
files: ["src/**/*.test.{ts,tsx}"],
|
|
10
|
+
env: {
|
|
11
|
+
"jest/globals": true,
|
|
12
|
+
},
|
|
13
|
+
plugins: ["jest"],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
rules: {
|
|
17
|
+
"@typescript-eslint/no-empty-function": "off",
|
|
18
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
19
|
+
},
|
|
20
|
+
};
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-present Ledger https://www.ledger.com/
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Coin-tester
|
|
2
|
+
|
|
3
|
+
## Getting started
|
|
4
|
+
|
|
5
|
+
### Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Docker](https://docs.docker.com/engine/install/)
|
|
8
|
+
|
|
9
|
+
### Build the speculos image
|
|
10
|
+
|
|
11
|
+
You only have to build speculos locally if you are on a Mac M1. Otherwise you can uncomment the image provided in the docker-compose.yml
|
|
12
|
+
|
|
13
|
+
1. Clone speculos repo
|
|
14
|
+
3. docker build -f build.Dockerfile -t speculos-builder:latest .
|
|
15
|
+
4 Patch Dockerfile:
|
|
16
|
+
```Dockerfile
|
|
17
|
+
# before
|
|
18
|
+
FROM ghcr.io/ledgerhq/speculos-builder:latest AS builder
|
|
19
|
+
# after
|
|
20
|
+
FROM speculos-builder:latest AS builder
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
4. docker build -f Dockerfile -t speculos:latest .
|
|
24
|
+
|
|
25
|
+
### Environment variables
|
|
26
|
+
|
|
27
|
+
Go in the coin-module you want to test and create a `.env` in the folder where your test resides.
|
|
28
|
+
For exemple for `coin-evm` create the file should be located in: `src/__test__/coin-test/.env`.
|
|
29
|
+
|
|
30
|
+
If availble you can copy the `.env.example`.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cp .env.example .env
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A `.env` should have at the very least the following attributes:
|
|
37
|
+
|
|
38
|
+
```conf
|
|
39
|
+
SEED=12 words
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Generate a new seed using [this tool](https://iancoleman.io/bip39/)
|
|
43
|
+
Generate a Github token as [described here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: '2.4'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
speculos:
|
|
5
|
+
# Speculos isn't supported for Mac M1 yet. You have to build the image locally as described in the README.
|
|
6
|
+
# Otherwise you can use the official image
|
|
7
|
+
# image: ghcr.io/ledgerhq/speculos:latest
|
|
8
|
+
image: ${SPECULOS_IMAGE:-ghcr.io/ledgerhq/speculos}:latest
|
|
9
|
+
volumes:
|
|
10
|
+
- ./lib/signers/tmp:/speculos/apps
|
|
11
|
+
ports:
|
|
12
|
+
- "${API_PORT}:${API_PORT}"
|
|
13
|
+
build:
|
|
14
|
+
context: /speculos
|
|
15
|
+
command: --model nanox ./apps/app.elf --seed="${SEED}" --display headless --api-port ${API_PORT}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
verbose: true,
|
|
4
|
+
preset: "ts-jest",
|
|
5
|
+
testEnvironment: "node",
|
|
6
|
+
testRegex: ".(test|spec).[jt]sx?$",
|
|
7
|
+
moduleDirectories: ["node_modules"],
|
|
8
|
+
setupFiles: ["dotenv/config"],
|
|
9
|
+
};
|
package/lib/main.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AccountBridge, Account, TransactionCommon, SignOperationEvent, CurrencyBridge } from "@ledgerhq/types-live";
|
|
2
|
+
export type ScenarioTransaction<T extends TransactionCommon> = Partial<T> & {
|
|
3
|
+
name: string;
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param previousAccount previousAccount returned by the latest sync before broadcasting this transaction
|
|
7
|
+
* @param currentAccount currentAccount synced after broadcasting this transaction
|
|
8
|
+
* @returns void
|
|
9
|
+
*/
|
|
10
|
+
expect?: (previousAccount: Account, currentAccount: Account) => void;
|
|
11
|
+
};
|
|
12
|
+
export type Scenario<T extends TransactionCommon> = {
|
|
13
|
+
name: string;
|
|
14
|
+
setup: () => Promise<{
|
|
15
|
+
accountBridge: AccountBridge<T>;
|
|
16
|
+
currencyBridge: CurrencyBridge;
|
|
17
|
+
account: Account;
|
|
18
|
+
retryInterval?: number;
|
|
19
|
+
retryLimit?: number;
|
|
20
|
+
onSignerConfirmation?: (e?: SignOperationEvent) => Promise<void>;
|
|
21
|
+
}>;
|
|
22
|
+
getTransactions: (address: string) => ScenarioTransaction<T>[];
|
|
23
|
+
beforeAll?: (account: Account) => Promise<void> | void;
|
|
24
|
+
afterAll?: (account: Account) => Promise<void> | void;
|
|
25
|
+
beforeEach?: (account: Account) => Promise<void> | void;
|
|
26
|
+
afterEach?: (account: Account) => Promise<void> | void;
|
|
27
|
+
teardown?: () => Promise<void> | void;
|
|
28
|
+
};
|
|
29
|
+
export declare function executeScenario<T extends TransactionCommon>(scenario: Scenario<T>): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,OAAO,EACP,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,KAAK,IAAI,CAAC;CACtE,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,iBAAiB,IAAI;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC;QACnB,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;QAChC,cAAc,EAAE,cAAc,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC,CAAC;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,wBAAsB,eAAe,CAAC,CAAC,SAAS,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAuMvF"}
|
package/lib/main.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.executeScenario = void 0;
|
|
16
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
+
const rxjs_1 = require("rxjs");
|
|
18
|
+
function executeScenario(scenario) {
|
|
19
|
+
var _a, _b, _c, _d, _e, _f;
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
try {
|
|
22
|
+
const { accountBridge, currencyBridge, account, retryInterval, retryLimit, onSignerConfirmation, } = yield scenario.setup();
|
|
23
|
+
console.log("Setup completed ✓");
|
|
24
|
+
console.log("\n");
|
|
25
|
+
console.log(chalk_1.default.bgBlue(" Address "), " → ", chalk_1.default.bold.blue(account.freshAddress), "\n\n");
|
|
26
|
+
const data = yield currencyBridge.preload(account.currency);
|
|
27
|
+
currencyBridge.hydrate(data, account.currency);
|
|
28
|
+
console.log("Preload + hydrate completed ✓");
|
|
29
|
+
console.log("Running a synchronization on the account...");
|
|
30
|
+
let scenarioAccount = yield (0, rxjs_1.firstValueFrom)(accountBridge
|
|
31
|
+
.sync(account, { paginationConfig: {} })
|
|
32
|
+
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), account)));
|
|
33
|
+
console.log("Synchronization completed ✓");
|
|
34
|
+
yield ((_a = scenario.beforeAll) === null || _a === void 0 ? void 0 : _a.call(scenario, scenarioAccount));
|
|
35
|
+
console.log("BeforeAll completed ✓");
|
|
36
|
+
console.log("\n\n");
|
|
37
|
+
console.log(chalk_1.default.bgCyan.black.bold(" ✧ "), " ", chalk_1.default.cyan(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgCyan.black.bold(" ✧ "), " → ", chalk_1.default.bold.cyan(" Starting ◌"));
|
|
38
|
+
const scenarioTransactions = scenario.getTransactions(account.freshAddress);
|
|
39
|
+
for (const testTransaction of scenarioTransactions) {
|
|
40
|
+
console.log("\n");
|
|
41
|
+
console.log(chalk_1.default.cyan("Transaction:", chalk_1.default.bold(testTransaction.name), "◌"));
|
|
42
|
+
(_b = scenario.beforeEach) === null || _b === void 0 ? void 0 : _b.call(scenario, scenarioAccount);
|
|
43
|
+
console.log("Before each ✔️");
|
|
44
|
+
if (scenarioTransactions.indexOf(testTransaction) > 0) {
|
|
45
|
+
scenarioAccount = yield (0, rxjs_1.firstValueFrom)(accountBridge
|
|
46
|
+
.sync(scenarioAccount, { paginationConfig: {} })
|
|
47
|
+
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), scenarioAccount)));
|
|
48
|
+
}
|
|
49
|
+
const previousAccount = Object.freeze(scenarioAccount);
|
|
50
|
+
const defaultTransaction = accountBridge.createTransaction(scenarioAccount);
|
|
51
|
+
const transaction = yield accountBridge.prepareTransaction(scenarioAccount, Object.assign(Object.assign({}, defaultTransaction), testTransaction));
|
|
52
|
+
console.log(" → ", "🧑🍳 ", chalk_1.default.bold("Prepared the transaction"), "✓");
|
|
53
|
+
const status = yield accountBridge.getTransactionStatus(scenarioAccount, transaction);
|
|
54
|
+
if (Object.entries(status.errors).length) {
|
|
55
|
+
throw new Error(`Error in transaction status: ${JSON.stringify(status.errors, null, 3)}`);
|
|
56
|
+
}
|
|
57
|
+
console.log(" → ", "🪲 ", chalk_1.default.bold("No status errors detected"), "✓");
|
|
58
|
+
const { signedOperation } = yield (0, rxjs_1.firstValueFrom)(accountBridge
|
|
59
|
+
.signOperation({
|
|
60
|
+
account: scenarioAccount,
|
|
61
|
+
transaction,
|
|
62
|
+
deviceId: "",
|
|
63
|
+
})
|
|
64
|
+
.pipe((0, rxjs_1.map)(e => {
|
|
65
|
+
if (e.type === "device-signature-requested") {
|
|
66
|
+
onSignerConfirmation === null || onSignerConfirmation === void 0 ? void 0 : onSignerConfirmation(e);
|
|
67
|
+
}
|
|
68
|
+
return e;
|
|
69
|
+
}), (0, rxjs_1.first)((e) => e.type === "signed")));
|
|
70
|
+
console.log(" → ", "🔏 ", chalk_1.default.bold("Signed the transaction"), "✓");
|
|
71
|
+
const optimisticOperation = yield accountBridge.broadcast({
|
|
72
|
+
account: scenarioAccount,
|
|
73
|
+
signedOperation,
|
|
74
|
+
});
|
|
75
|
+
console.log(" → ", "🛫 ", chalk_1.default.bold("Broadcasted the transaction"), "✓");
|
|
76
|
+
const retry_limit = retryLimit !== null && retryLimit !== void 0 ? retryLimit : 10;
|
|
77
|
+
const expectHandler = (retry) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
var _g, _h;
|
|
79
|
+
scenarioAccount = yield (0, rxjs_1.firstValueFrom)(accountBridge
|
|
80
|
+
.sync(Object.assign(Object.assign({}, scenarioAccount), { pendingOperations: [optimisticOperation] }), { paginationConfig: {} })
|
|
81
|
+
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), scenarioAccount)));
|
|
82
|
+
if (!testTransaction.expect) {
|
|
83
|
+
console.warn(chalk_1.default.yellow(`No expects in the transaction ${chalk_1.default.bold(testTransaction.name)}. You might want to add tests in this transaction.`));
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
(_g = testTransaction.expect) === null || _g === void 0 ? void 0 : _g.call(testTransaction, previousAccount, scenarioAccount);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (!((_h = err === null || err === void 0 ? void 0 : err.matcherResult) === null || _h === void 0 ? void 0 : _h.pass)) {
|
|
90
|
+
if (retry === 0) {
|
|
91
|
+
console.error(chalk_1.default.red(`Retried 10 times and could not assert all expects for transaction ${chalk_1.default.bold(testTransaction.name)}`));
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
console.warn(chalk_1.default.magenta("Test asssertion failed. Retrying..."));
|
|
95
|
+
yield new Promise(resolve => setTimeout(resolve, retryInterval !== null && retryInterval !== void 0 ? retryInterval : 5000));
|
|
96
|
+
yield expectHandler(retry - 1);
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
yield expectHandler(retry_limit);
|
|
102
|
+
(_c = scenario.afterEach) === null || _c === void 0 ? void 0 : _c.call(scenario, scenarioAccount);
|
|
103
|
+
console.log("After each ✔️");
|
|
104
|
+
console.log(chalk_1.default.green("Transaction:", chalk_1.default.bold(testTransaction.name), "completed ✓"));
|
|
105
|
+
}
|
|
106
|
+
console.log("\n");
|
|
107
|
+
yield ((_d = scenario.afterAll) === null || _d === void 0 ? void 0 : _d.call(scenario, scenarioAccount));
|
|
108
|
+
console.log("afterAll completed ✓");
|
|
109
|
+
console.log("Stopping engine...");
|
|
110
|
+
yield ((_e = scenario.teardown) === null || _e === void 0 ? void 0 : _e.call(scenario));
|
|
111
|
+
console.log("\n\n", chalk_1.default.bgGreen.black.bold(" ✧ "), " ", chalk_1.default.green(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgGreen.black.bold(" ✧ "), " → ", chalk_1.default.bold.green(" Completed 🎉"), "\n\n");
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error("\n\n", chalk_1.default.bgRed.black.bold(" ✧ "), " ", chalk_1.default.red(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgRed.black.bold(" ✧ "), " → ", chalk_1.default.bold.red(" Failed ❌"), "\n\n");
|
|
115
|
+
yield ((_f = scenario.teardown) === null || _f === void 0 ? void 0 : _f.call(scenario));
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
exports.executeScenario = executeScenario;
|
|
121
|
+
//# sourceMappingURL=main.js.map
|
package/lib/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import SpeculosTransportHttp from "@ledgerhq/hw-transport-node-speculos-http";
|
|
2
|
+
import { SignOperationEvent } from "@ledgerhq/types-live";
|
|
3
|
+
export declare const spawnSpeculos: (nanoAppEndpoint: `/${string}`) => Promise<{
|
|
4
|
+
transport: SpeculosTransportHttp;
|
|
5
|
+
onSignerConfirmation: (e?: SignOperationEvent) => Promise<void>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const killSpeculos: () => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=speculos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculos.d.ts","sourceRoot":"","sources":["../../src/signers/speculos.ts"],"names":[],"mappings":"AAIA,OAAO,qBAAqB,MAAM,2CAA2C,CAAC;AAG9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO1D,eAAO,MAAM,aAAa,oBACP,IAAI,MAAM,EAAE;eAElB,qBAAqB;+BACL,kBAAkB,KAAK,QAAQ,IAAI,CAAC;EA6DhE,CAAC;AAEF,eAAO,MAAM,YAAY,qBAOxB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.killSpeculos = exports.spawnSpeculos = void 0;
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const axios_1 = __importDefault(require("axios"));
|
|
18
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
19
|
+
const docker_compose_1 = require("docker-compose");
|
|
20
|
+
const hw_transport_node_speculos_http_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos-http"));
|
|
21
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
22
|
+
const { API_PORT } = process.env;
|
|
23
|
+
const cwd = path_1.default.join(__dirname);
|
|
24
|
+
const delay = (timing) => new Promise(resolve => setTimeout(resolve, timing));
|
|
25
|
+
const spawnSpeculos = (nanoAppEndpoint) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
+
console.log(`Starting speculos...`);
|
|
27
|
+
const { data: blob } = yield (0, axios_1.default)({
|
|
28
|
+
url: `https://raw.githubusercontent.com/LedgerHQ/coin-apps/master/nanox${nanoAppEndpoint}`,
|
|
29
|
+
method: "GET",
|
|
30
|
+
responseType: "stream",
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${process.env.GH_TOKEN}`,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
yield promises_1.default.mkdir(path_1.default.resolve(cwd, "tmp"), { recursive: true });
|
|
36
|
+
yield promises_1.default.writeFile(path_1.default.resolve(cwd, "tmp/app.elf"), blob, "binary");
|
|
37
|
+
yield docker_compose_1.v2.upOne("speculos", {
|
|
38
|
+
cwd,
|
|
39
|
+
log: true,
|
|
40
|
+
env: process.env,
|
|
41
|
+
});
|
|
42
|
+
const checkSpeculosLogs = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
const { out } = yield docker_compose_1.v2.logs("speculos", { cwd, env: process.env });
|
|
44
|
+
if (out.includes("Running on all addresses (0.0.0.0)")) {
|
|
45
|
+
console.log(chalk_1.default.bgYellowBright.black(" - SPECULOS READY ✅ - "));
|
|
46
|
+
return hw_transport_node_speculos_http_1.default.open({
|
|
47
|
+
apiPort: API_PORT,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
yield delay(200);
|
|
51
|
+
return checkSpeculosLogs();
|
|
52
|
+
});
|
|
53
|
+
const onSpeculosConfirmation = (e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
if ((e === null || e === void 0 ? void 0 : e.type) === "device-signature-requested") {
|
|
55
|
+
const { data } = yield axios_1.default.get(`http://localhost:${process.env.API_PORT}/events?currentscreenonly=true`);
|
|
56
|
+
if (data.events[0].text !== "Accept") {
|
|
57
|
+
yield axios_1.default.post(`http://localhost:${process.env.API_PORT}/button/right`, {
|
|
58
|
+
action: "press-and-release",
|
|
59
|
+
});
|
|
60
|
+
onSpeculosConfirmation(e);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
yield axios_1.default.post(`http://localhost:${process.env.API_PORT}/button/both`, {
|
|
64
|
+
action: "press-and-release",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return checkSpeculosLogs().then(transport => {
|
|
70
|
+
return {
|
|
71
|
+
transport,
|
|
72
|
+
onSignerConfirmation: onSpeculosConfirmation,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
exports.spawnSpeculos = spawnSpeculos;
|
|
77
|
+
const killSpeculos = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
+
console.log("Stopping speculos...");
|
|
79
|
+
yield docker_compose_1.v2.down({
|
|
80
|
+
cwd,
|
|
81
|
+
log: true,
|
|
82
|
+
env: process.env,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
exports.killSpeculos = killSpeculos;
|
|
86
|
+
["exit", "SIGINT", "SIGQUIT", "SIGTERM", "SIGUSR1", "SIGUSR2", "uncaughtException"].map(e => process.on(e, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
|
+
yield (0, exports.killSpeculos)();
|
|
88
|
+
})));
|
|
89
|
+
//# sourceMappingURL=speculos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculos.js","sourceRoot":"","sources":["../../src/signers/speculos.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/lib-es/main.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AccountBridge, Account, TransactionCommon, SignOperationEvent, CurrencyBridge } from "@ledgerhq/types-live";
|
|
2
|
+
export type ScenarioTransaction<T extends TransactionCommon> = Partial<T> & {
|
|
3
|
+
name: string;
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param previousAccount previousAccount returned by the latest sync before broadcasting this transaction
|
|
7
|
+
* @param currentAccount currentAccount synced after broadcasting this transaction
|
|
8
|
+
* @returns void
|
|
9
|
+
*/
|
|
10
|
+
expect?: (previousAccount: Account, currentAccount: Account) => void;
|
|
11
|
+
};
|
|
12
|
+
export type Scenario<T extends TransactionCommon> = {
|
|
13
|
+
name: string;
|
|
14
|
+
setup: () => Promise<{
|
|
15
|
+
accountBridge: AccountBridge<T>;
|
|
16
|
+
currencyBridge: CurrencyBridge;
|
|
17
|
+
account: Account;
|
|
18
|
+
retryInterval?: number;
|
|
19
|
+
retryLimit?: number;
|
|
20
|
+
onSignerConfirmation?: (e?: SignOperationEvent) => Promise<void>;
|
|
21
|
+
}>;
|
|
22
|
+
getTransactions: (address: string) => ScenarioTransaction<T>[];
|
|
23
|
+
beforeAll?: (account: Account) => Promise<void> | void;
|
|
24
|
+
afterAll?: (account: Account) => Promise<void> | void;
|
|
25
|
+
beforeEach?: (account: Account) => Promise<void> | void;
|
|
26
|
+
afterEach?: (account: Account) => Promise<void> | void;
|
|
27
|
+
teardown?: () => Promise<void> | void;
|
|
28
|
+
};
|
|
29
|
+
export declare function executeScenario<T extends TransactionCommon>(scenario: Scenario<T>): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,OAAO,EACP,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,KAAK,IAAI,CAAC;CACtE,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,iBAAiB,IAAI;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC;QACnB,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;QAChC,cAAc,EAAE,cAAc,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC,CAAC;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,wBAAsB,eAAe,CAAC,CAAC,SAAS,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAuMvF"}
|
package/lib-es/main.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { first, firstValueFrom, map, reduce } from "rxjs";
|
|
12
|
+
export function executeScenario(scenario) {
|
|
13
|
+
var _a, _b, _c, _d, _e, _f;
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
try {
|
|
16
|
+
const { accountBridge, currencyBridge, account, retryInterval, retryLimit, onSignerConfirmation, } = yield scenario.setup();
|
|
17
|
+
console.log("Setup completed ✓");
|
|
18
|
+
console.log("\n");
|
|
19
|
+
console.log(chalk.bgBlue(" Address "), " → ", chalk.bold.blue(account.freshAddress), "\n\n");
|
|
20
|
+
const data = yield currencyBridge.preload(account.currency);
|
|
21
|
+
currencyBridge.hydrate(data, account.currency);
|
|
22
|
+
console.log("Preload + hydrate completed ✓");
|
|
23
|
+
console.log("Running a synchronization on the account...");
|
|
24
|
+
let scenarioAccount = yield firstValueFrom(accountBridge
|
|
25
|
+
.sync(account, { paginationConfig: {} })
|
|
26
|
+
.pipe(reduce((acc, f) => f(acc), account)));
|
|
27
|
+
console.log("Synchronization completed ✓");
|
|
28
|
+
yield ((_a = scenario.beforeAll) === null || _a === void 0 ? void 0 : _a.call(scenario, scenarioAccount));
|
|
29
|
+
console.log("BeforeAll completed ✓");
|
|
30
|
+
console.log("\n\n");
|
|
31
|
+
console.log(chalk.bgCyan.black.bold(" ✧ "), " ", chalk.cyan(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgCyan.black.bold(" ✧ "), " → ", chalk.bold.cyan(" Starting ◌"));
|
|
32
|
+
const scenarioTransactions = scenario.getTransactions(account.freshAddress);
|
|
33
|
+
for (const testTransaction of scenarioTransactions) {
|
|
34
|
+
console.log("\n");
|
|
35
|
+
console.log(chalk.cyan("Transaction:", chalk.bold(testTransaction.name), "◌"));
|
|
36
|
+
(_b = scenario.beforeEach) === null || _b === void 0 ? void 0 : _b.call(scenario, scenarioAccount);
|
|
37
|
+
console.log("Before each ✔️");
|
|
38
|
+
if (scenarioTransactions.indexOf(testTransaction) > 0) {
|
|
39
|
+
scenarioAccount = yield firstValueFrom(accountBridge
|
|
40
|
+
.sync(scenarioAccount, { paginationConfig: {} })
|
|
41
|
+
.pipe(reduce((acc, f) => f(acc), scenarioAccount)));
|
|
42
|
+
}
|
|
43
|
+
const previousAccount = Object.freeze(scenarioAccount);
|
|
44
|
+
const defaultTransaction = accountBridge.createTransaction(scenarioAccount);
|
|
45
|
+
const transaction = yield accountBridge.prepareTransaction(scenarioAccount, Object.assign(Object.assign({}, defaultTransaction), testTransaction));
|
|
46
|
+
console.log(" → ", "🧑🍳 ", chalk.bold("Prepared the transaction"), "✓");
|
|
47
|
+
const status = yield accountBridge.getTransactionStatus(scenarioAccount, transaction);
|
|
48
|
+
if (Object.entries(status.errors).length) {
|
|
49
|
+
throw new Error(`Error in transaction status: ${JSON.stringify(status.errors, null, 3)}`);
|
|
50
|
+
}
|
|
51
|
+
console.log(" → ", "🪲 ", chalk.bold("No status errors detected"), "✓");
|
|
52
|
+
const { signedOperation } = yield firstValueFrom(accountBridge
|
|
53
|
+
.signOperation({
|
|
54
|
+
account: scenarioAccount,
|
|
55
|
+
transaction,
|
|
56
|
+
deviceId: "",
|
|
57
|
+
})
|
|
58
|
+
.pipe(map(e => {
|
|
59
|
+
if (e.type === "device-signature-requested") {
|
|
60
|
+
onSignerConfirmation === null || onSignerConfirmation === void 0 ? void 0 : onSignerConfirmation(e);
|
|
61
|
+
}
|
|
62
|
+
return e;
|
|
63
|
+
}), first((e) => e.type === "signed")));
|
|
64
|
+
console.log(" → ", "🔏 ", chalk.bold("Signed the transaction"), "✓");
|
|
65
|
+
const optimisticOperation = yield accountBridge.broadcast({
|
|
66
|
+
account: scenarioAccount,
|
|
67
|
+
signedOperation,
|
|
68
|
+
});
|
|
69
|
+
console.log(" → ", "🛫 ", chalk.bold("Broadcasted the transaction"), "✓");
|
|
70
|
+
const retry_limit = retryLimit !== null && retryLimit !== void 0 ? retryLimit : 10;
|
|
71
|
+
const expectHandler = (retry) => __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
var _g, _h;
|
|
73
|
+
scenarioAccount = yield firstValueFrom(accountBridge
|
|
74
|
+
.sync(Object.assign(Object.assign({}, scenarioAccount), { pendingOperations: [optimisticOperation] }), { paginationConfig: {} })
|
|
75
|
+
.pipe(reduce((acc, f) => f(acc), scenarioAccount)));
|
|
76
|
+
if (!testTransaction.expect) {
|
|
77
|
+
console.warn(chalk.yellow(`No expects in the transaction ${chalk.bold(testTransaction.name)}. You might want to add tests in this transaction.`));
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
(_g = testTransaction.expect) === null || _g === void 0 ? void 0 : _g.call(testTransaction, previousAccount, scenarioAccount);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (!((_h = err === null || err === void 0 ? void 0 : err.matcherResult) === null || _h === void 0 ? void 0 : _h.pass)) {
|
|
84
|
+
if (retry === 0) {
|
|
85
|
+
console.error(chalk.red(`Retried 10 times and could not assert all expects for transaction ${chalk.bold(testTransaction.name)}`));
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
console.warn(chalk.magenta("Test asssertion failed. Retrying..."));
|
|
89
|
+
yield new Promise(resolve => setTimeout(resolve, retryInterval !== null && retryInterval !== void 0 ? retryInterval : 5000));
|
|
90
|
+
yield expectHandler(retry - 1);
|
|
91
|
+
}
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
yield expectHandler(retry_limit);
|
|
96
|
+
(_c = scenario.afterEach) === null || _c === void 0 ? void 0 : _c.call(scenario, scenarioAccount);
|
|
97
|
+
console.log("After each ✔️");
|
|
98
|
+
console.log(chalk.green("Transaction:", chalk.bold(testTransaction.name), "completed ✓"));
|
|
99
|
+
}
|
|
100
|
+
console.log("\n");
|
|
101
|
+
yield ((_d = scenario.afterAll) === null || _d === void 0 ? void 0 : _d.call(scenario, scenarioAccount));
|
|
102
|
+
console.log("afterAll completed ✓");
|
|
103
|
+
console.log("Stopping engine...");
|
|
104
|
+
yield ((_e = scenario.teardown) === null || _e === void 0 ? void 0 : _e.call(scenario));
|
|
105
|
+
console.log("\n\n", chalk.bgGreen.black.bold(" ✧ "), " ", chalk.green(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgGreen.black.bold(" ✧ "), " → ", chalk.bold.green(" Completed 🎉"), "\n\n");
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error("\n\n", chalk.bgRed.black.bold(" ✧ "), " ", chalk.red(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgRed.black.bold(" ✧ "), " → ", chalk.bold.red(" Failed ❌"), "\n\n");
|
|
109
|
+
yield ((_f = scenario.teardown) === null || _f === void 0 ? void 0 : _f.call(scenario));
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import SpeculosTransportHttp from "@ledgerhq/hw-transport-node-speculos-http";
|
|
2
|
+
import { SignOperationEvent } from "@ledgerhq/types-live";
|
|
3
|
+
export declare const spawnSpeculos: (nanoAppEndpoint: `/${string}`) => Promise<{
|
|
4
|
+
transport: SpeculosTransportHttp;
|
|
5
|
+
onSignerConfirmation: (e?: SignOperationEvent) => Promise<void>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const killSpeculos: () => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=speculos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculos.d.ts","sourceRoot":"","sources":["../../src/signers/speculos.ts"],"names":[],"mappings":"AAIA,OAAO,qBAAqB,MAAM,2CAA2C,CAAC;AAG9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO1D,eAAO,MAAM,aAAa,oBACP,IAAI,MAAM,EAAE;eAElB,qBAAqB;+BACL,kBAAkB,KAAK,QAAQ,IAAI,CAAC;EA6DhE,CAAC;AAEF,eAAO,MAAM,YAAY,qBAOxB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import path from "path";
|
|
11
|
+
import axios from "axios";
|
|
12
|
+
import fs from "fs/promises";
|
|
13
|
+
import { v2 as compose } from "docker-compose";
|
|
14
|
+
import SpeculosTransportHttp from "@ledgerhq/hw-transport-node-speculos-http";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
const { API_PORT } = process.env;
|
|
17
|
+
const cwd = path.join(__dirname);
|
|
18
|
+
const delay = (timing) => new Promise(resolve => setTimeout(resolve, timing));
|
|
19
|
+
export const spawnSpeculos = (nanoAppEndpoint) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
console.log(`Starting speculos...`);
|
|
21
|
+
const { data: blob } = yield axios({
|
|
22
|
+
url: `https://raw.githubusercontent.com/LedgerHQ/coin-apps/master/nanox${nanoAppEndpoint}`,
|
|
23
|
+
method: "GET",
|
|
24
|
+
responseType: "stream",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${process.env.GH_TOKEN}`,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
yield fs.mkdir(path.resolve(cwd, "tmp"), { recursive: true });
|
|
30
|
+
yield fs.writeFile(path.resolve(cwd, "tmp/app.elf"), blob, "binary");
|
|
31
|
+
yield compose.upOne("speculos", {
|
|
32
|
+
cwd,
|
|
33
|
+
log: true,
|
|
34
|
+
env: process.env,
|
|
35
|
+
});
|
|
36
|
+
const checkSpeculosLogs = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
|
+
const { out } = yield compose.logs("speculos", { cwd, env: process.env });
|
|
38
|
+
if (out.includes("Running on all addresses (0.0.0.0)")) {
|
|
39
|
+
console.log(chalk.bgYellowBright.black(" - SPECULOS READY ✅ - "));
|
|
40
|
+
return SpeculosTransportHttp.open({
|
|
41
|
+
apiPort: API_PORT,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
yield delay(200);
|
|
45
|
+
return checkSpeculosLogs();
|
|
46
|
+
});
|
|
47
|
+
const onSpeculosConfirmation = (e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
if ((e === null || e === void 0 ? void 0 : e.type) === "device-signature-requested") {
|
|
49
|
+
const { data } = yield axios.get(`http://localhost:${process.env.API_PORT}/events?currentscreenonly=true`);
|
|
50
|
+
if (data.events[0].text !== "Accept") {
|
|
51
|
+
yield axios.post(`http://localhost:${process.env.API_PORT}/button/right`, {
|
|
52
|
+
action: "press-and-release",
|
|
53
|
+
});
|
|
54
|
+
onSpeculosConfirmation(e);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
yield axios.post(`http://localhost:${process.env.API_PORT}/button/both`, {
|
|
58
|
+
action: "press-and-release",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return checkSpeculosLogs().then(transport => {
|
|
64
|
+
return {
|
|
65
|
+
transport,
|
|
66
|
+
onSignerConfirmation: onSpeculosConfirmation,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
export const killSpeculos = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
71
|
+
console.log("Stopping speculos...");
|
|
72
|
+
yield compose.down({
|
|
73
|
+
cwd,
|
|
74
|
+
log: true,
|
|
75
|
+
env: process.env,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
["exit", "SIGINT", "SIGQUIT", "SIGTERM", "SIGUSR1", "SIGUSR2", "uncaughtException"].map(e => process.on(e, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
79
|
+
yield killSpeculos();
|
|
80
|
+
})));
|
|
81
|
+
//# sourceMappingURL=speculos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculos.js","sourceRoot":"","sources":["../../src/signers/speculos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
package/lib-es/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ledgerhq/coin-tester",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic testing of Ledger coin-modules",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"axios": "^1.5.0",
|
|
8
|
+
"chalk": "^4.1.2",
|
|
9
|
+
"docker-compose": "^0.24.2",
|
|
10
|
+
"lodash": "^4.17.21",
|
|
11
|
+
"rxjs": "7.8.1",
|
|
12
|
+
"@ledgerhq/hw-transport-node-speculos-http": "^6.28.6"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/jest": "^28.1.8",
|
|
16
|
+
"@types/node": "^18.15.7",
|
|
17
|
+
"jest": "^28.1.3",
|
|
18
|
+
"ts-jest": "^28.0.8",
|
|
19
|
+
"typescript": "^5.1.3",
|
|
20
|
+
"@ledgerhq/types-cryptoassets": "^7.11.0",
|
|
21
|
+
"@ledgerhq/types-live": "^6.46.1-nightly.2"
|
|
22
|
+
},
|
|
23
|
+
"typesVersions": {
|
|
24
|
+
"*": {
|
|
25
|
+
"lib/*": [
|
|
26
|
+
"lib/*"
|
|
27
|
+
],
|
|
28
|
+
"lib-es/*": [
|
|
29
|
+
"lib-es/*"
|
|
30
|
+
],
|
|
31
|
+
"*": [
|
|
32
|
+
"lib/*"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
"./lib/*": "./lib/*.js",
|
|
38
|
+
"./lib-es/*": "./lib-es/*.js",
|
|
39
|
+
"./*": {
|
|
40
|
+
"require": "./lib/*.js",
|
|
41
|
+
"default": "./lib-es/*.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"Ledger",
|
|
47
|
+
"LedgerWallet",
|
|
48
|
+
"evm",
|
|
49
|
+
"Ethereum",
|
|
50
|
+
"Hardware Wallet"
|
|
51
|
+
],
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/LedgerHQ/ledger-live.git"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/LedgerHQ/ledger-live/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-tester",
|
|
60
|
+
"scripts": {
|
|
61
|
+
"clean": "rimraf lib lib-es",
|
|
62
|
+
"build": "tsc && tsc -m ES6 --outDir lib-es",
|
|
63
|
+
"prewatch": "pnpm build",
|
|
64
|
+
"watch": "tsc --watch",
|
|
65
|
+
"lint": "eslint .",
|
|
66
|
+
"lint:fix": "pnpm lint --fix"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountBridge,
|
|
3
|
+
Account,
|
|
4
|
+
TransactionCommon,
|
|
5
|
+
SignOperationEvent,
|
|
6
|
+
CurrencyBridge,
|
|
7
|
+
} from "@ledgerhq/types-live";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { first, firstValueFrom, map, reduce } from "rxjs";
|
|
10
|
+
|
|
11
|
+
export type ScenarioTransaction<T extends TransactionCommon> = Partial<T> & {
|
|
12
|
+
name: string;
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param previousAccount previousAccount returned by the latest sync before broadcasting this transaction
|
|
16
|
+
* @param currentAccount currentAccount synced after broadcasting this transaction
|
|
17
|
+
* @returns void
|
|
18
|
+
*/
|
|
19
|
+
expect?: (previousAccount: Account, currentAccount: Account) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type Scenario<T extends TransactionCommon> = {
|
|
23
|
+
name: string;
|
|
24
|
+
setup: () => Promise<{
|
|
25
|
+
accountBridge: AccountBridge<T>;
|
|
26
|
+
currencyBridge: CurrencyBridge;
|
|
27
|
+
account: Account;
|
|
28
|
+
retryInterval?: number;
|
|
29
|
+
retryLimit?: number;
|
|
30
|
+
onSignerConfirmation?: (e?: SignOperationEvent) => Promise<void>;
|
|
31
|
+
}>;
|
|
32
|
+
getTransactions: (address: string) => ScenarioTransaction<T>[];
|
|
33
|
+
beforeAll?: (account: Account) => Promise<void> | void;
|
|
34
|
+
afterAll?: (account: Account) => Promise<void> | void;
|
|
35
|
+
beforeEach?: (account: Account) => Promise<void> | void;
|
|
36
|
+
afterEach?: (account: Account) => Promise<void> | void;
|
|
37
|
+
teardown?: () => Promise<void> | void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export async function executeScenario<T extends TransactionCommon>(scenario: Scenario<T>) {
|
|
41
|
+
try {
|
|
42
|
+
const {
|
|
43
|
+
accountBridge,
|
|
44
|
+
currencyBridge,
|
|
45
|
+
account,
|
|
46
|
+
retryInterval,
|
|
47
|
+
retryLimit,
|
|
48
|
+
onSignerConfirmation,
|
|
49
|
+
} = await scenario.setup();
|
|
50
|
+
|
|
51
|
+
console.log("Setup completed ✓");
|
|
52
|
+
|
|
53
|
+
console.log("\n");
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.bgBlue(" Address "),
|
|
56
|
+
" → ",
|
|
57
|
+
chalk.bold.blue(account.freshAddress),
|
|
58
|
+
"\n\n",
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const data = await currencyBridge.preload(account.currency);
|
|
62
|
+
currencyBridge.hydrate(data, account.currency);
|
|
63
|
+
console.log("Preload + hydrate completed ✓");
|
|
64
|
+
|
|
65
|
+
console.log("Running a synchronization on the account...");
|
|
66
|
+
let scenarioAccount = await firstValueFrom(
|
|
67
|
+
accountBridge
|
|
68
|
+
.sync(account, { paginationConfig: {} })
|
|
69
|
+
.pipe(reduce((acc, f) => f(acc), account)),
|
|
70
|
+
);
|
|
71
|
+
console.log("Synchronization completed ✓");
|
|
72
|
+
|
|
73
|
+
await scenario.beforeAll?.(scenarioAccount);
|
|
74
|
+
console.log("BeforeAll completed ✓");
|
|
75
|
+
|
|
76
|
+
console.log("\n\n");
|
|
77
|
+
console.log(
|
|
78
|
+
chalk.bgCyan.black.bold(" ✧ "),
|
|
79
|
+
" ",
|
|
80
|
+
chalk.cyan(`Scenario: ${chalk.italic.bold(scenario.name)}`),
|
|
81
|
+
|
|
82
|
+
" ",
|
|
83
|
+
chalk.bgCyan.black.bold(" ✧ "),
|
|
84
|
+
" → ",
|
|
85
|
+
chalk.bold.cyan(" Starting ◌"),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const scenarioTransactions = scenario.getTransactions(account.freshAddress);
|
|
89
|
+
|
|
90
|
+
for (const testTransaction of scenarioTransactions) {
|
|
91
|
+
console.log("\n");
|
|
92
|
+
console.log(chalk.cyan("Transaction:", chalk.bold(testTransaction.name), "◌"));
|
|
93
|
+
|
|
94
|
+
scenario.beforeEach?.(scenarioAccount);
|
|
95
|
+
console.log("Before each ✔️");
|
|
96
|
+
|
|
97
|
+
if (scenarioTransactions.indexOf(testTransaction) > 0) {
|
|
98
|
+
scenarioAccount = await firstValueFrom(
|
|
99
|
+
accountBridge
|
|
100
|
+
.sync(scenarioAccount, { paginationConfig: {} })
|
|
101
|
+
.pipe(reduce((acc, f) => f(acc), scenarioAccount)),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const previousAccount = Object.freeze(scenarioAccount);
|
|
106
|
+
|
|
107
|
+
const defaultTransaction = accountBridge.createTransaction(scenarioAccount);
|
|
108
|
+
const transaction = await accountBridge.prepareTransaction(scenarioAccount, {
|
|
109
|
+
...defaultTransaction,
|
|
110
|
+
...testTransaction,
|
|
111
|
+
} as T);
|
|
112
|
+
|
|
113
|
+
console.log(" → ", "🧑🍳 ", chalk.bold("Prepared the transaction"), "✓");
|
|
114
|
+
|
|
115
|
+
const status = await accountBridge.getTransactionStatus(scenarioAccount, transaction);
|
|
116
|
+
if (Object.entries(status.errors).length) {
|
|
117
|
+
throw new Error(`Error in transaction status: ${JSON.stringify(status.errors, null, 3)}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(" → ", "🪲 ", chalk.bold("No status errors detected"), "✓");
|
|
121
|
+
|
|
122
|
+
const { signedOperation } = await firstValueFrom(
|
|
123
|
+
accountBridge
|
|
124
|
+
.signOperation({
|
|
125
|
+
account: scenarioAccount,
|
|
126
|
+
transaction,
|
|
127
|
+
deviceId: "",
|
|
128
|
+
})
|
|
129
|
+
.pipe(
|
|
130
|
+
map(e => {
|
|
131
|
+
if (e.type === "device-signature-requested") {
|
|
132
|
+
onSignerConfirmation?.(e);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return e;
|
|
136
|
+
}),
|
|
137
|
+
first((e): e is SignOperationEvent & { type: "signed" } => e.type === "signed"),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
console.log(" → ", "🔏 ", chalk.bold("Signed the transaction"), "✓");
|
|
142
|
+
|
|
143
|
+
const optimisticOperation = await accountBridge.broadcast({
|
|
144
|
+
account: scenarioAccount,
|
|
145
|
+
signedOperation,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
console.log(" → ", "🛫 ", chalk.bold("Broadcasted the transaction"), "✓");
|
|
149
|
+
|
|
150
|
+
const retry_limit = retryLimit ?? 10;
|
|
151
|
+
|
|
152
|
+
const expectHandler = async (retry: number) => {
|
|
153
|
+
scenarioAccount = await firstValueFrom(
|
|
154
|
+
accountBridge
|
|
155
|
+
.sync(
|
|
156
|
+
{ ...scenarioAccount, pendingOperations: [optimisticOperation] },
|
|
157
|
+
{ paginationConfig: {} },
|
|
158
|
+
)
|
|
159
|
+
.pipe(reduce((acc, f) => f(acc), scenarioAccount)),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!testTransaction.expect) {
|
|
163
|
+
console.warn(
|
|
164
|
+
chalk.yellow(
|
|
165
|
+
`No expects in the transaction ${chalk.bold(
|
|
166
|
+
testTransaction.name,
|
|
167
|
+
)}. You might want to add tests in this transaction.`,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
testTransaction.expect?.(previousAccount, scenarioAccount);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (!(err as { matcherResult?: { pass: boolean } })?.matcherResult?.pass) {
|
|
176
|
+
if (retry === 0) {
|
|
177
|
+
console.error(
|
|
178
|
+
chalk.red(
|
|
179
|
+
`Retried 10 times and could not assert all expects for transaction ${chalk.bold(
|
|
180
|
+
testTransaction.name,
|
|
181
|
+
)}`,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.warn(chalk.magenta("Test asssertion failed. Retrying..."));
|
|
189
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval ?? 5000));
|
|
190
|
+
await expectHandler(retry - 1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
await expectHandler(retry_limit);
|
|
198
|
+
|
|
199
|
+
scenario.afterEach?.(scenarioAccount);
|
|
200
|
+
console.log("After each ✔️");
|
|
201
|
+
console.log(chalk.green("Transaction:", chalk.bold(testTransaction.name), "completed ✓"));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log("\n");
|
|
205
|
+
|
|
206
|
+
await scenario.afterAll?.(scenarioAccount);
|
|
207
|
+
console.log("afterAll completed ✓");
|
|
208
|
+
console.log("Stopping engine...");
|
|
209
|
+
await scenario.teardown?.();
|
|
210
|
+
|
|
211
|
+
console.log(
|
|
212
|
+
"\n\n",
|
|
213
|
+
chalk.bgGreen.black.bold(" ✧ "),
|
|
214
|
+
" ",
|
|
215
|
+
chalk.green(`Scenario: ${chalk.italic.bold(scenario.name)}`),
|
|
216
|
+
" ",
|
|
217
|
+
chalk.bgGreen.black.bold(" ✧ "),
|
|
218
|
+
" → ",
|
|
219
|
+
chalk.bold.green(" Completed 🎉"),
|
|
220
|
+
"\n\n",
|
|
221
|
+
);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error(
|
|
224
|
+
"\n\n",
|
|
225
|
+
chalk.bgRed.black.bold(" ✧ "),
|
|
226
|
+
" ",
|
|
227
|
+
chalk.red(`Scenario: ${chalk.italic.bold(scenario.name)}`),
|
|
228
|
+
" ",
|
|
229
|
+
chalk.bgRed.black.bold(" ✧ "),
|
|
230
|
+
" → ",
|
|
231
|
+
chalk.bold.red(" Failed ❌"),
|
|
232
|
+
"\n\n",
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await scenario.teardown?.();
|
|
236
|
+
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import { v2 as compose } from "docker-compose";
|
|
5
|
+
import SpeculosTransportHttp from "@ledgerhq/hw-transport-node-speculos-http";
|
|
6
|
+
import { ENV } from "../types";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { SignOperationEvent } from "@ledgerhq/types-live";
|
|
9
|
+
|
|
10
|
+
const { API_PORT } = process.env as ENV;
|
|
11
|
+
const cwd = path.join(__dirname);
|
|
12
|
+
|
|
13
|
+
const delay = (timing: number) => new Promise(resolve => setTimeout(resolve, timing));
|
|
14
|
+
|
|
15
|
+
export const spawnSpeculos = async (
|
|
16
|
+
nanoAppEndpoint: `/${string}`,
|
|
17
|
+
): Promise<{
|
|
18
|
+
transport: SpeculosTransportHttp;
|
|
19
|
+
onSignerConfirmation: (e?: SignOperationEvent) => Promise<void>;
|
|
20
|
+
}> => {
|
|
21
|
+
console.log(`Starting speculos...`);
|
|
22
|
+
|
|
23
|
+
const { data: blob } = await axios({
|
|
24
|
+
url: `https://raw.githubusercontent.com/LedgerHQ/coin-apps/master/nanox${nanoAppEndpoint}`,
|
|
25
|
+
method: "GET",
|
|
26
|
+
responseType: "stream",
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${process.env.GH_TOKEN}`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await fs.mkdir(path.resolve(cwd, "tmp"), { recursive: true });
|
|
33
|
+
await fs.writeFile(path.resolve(cwd, "tmp/app.elf"), blob, "binary");
|
|
34
|
+
|
|
35
|
+
await compose.upOne("speculos", {
|
|
36
|
+
cwd,
|
|
37
|
+
log: true,
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const checkSpeculosLogs = async (): Promise<SpeculosTransportHttp> => {
|
|
42
|
+
const { out } = await compose.logs("speculos", { cwd, env: process.env });
|
|
43
|
+
|
|
44
|
+
if (out.includes("Running on all addresses (0.0.0.0)")) {
|
|
45
|
+
console.log(chalk.bgYellowBright.black(" - SPECULOS READY ✅ - "));
|
|
46
|
+
return SpeculosTransportHttp.open({
|
|
47
|
+
apiPort: API_PORT,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await delay(200);
|
|
52
|
+
return checkSpeculosLogs();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onSpeculosConfirmation = async (e?: SignOperationEvent): Promise<void> => {
|
|
56
|
+
if (e?.type === "device-signature-requested") {
|
|
57
|
+
const { data } = await axios.get(
|
|
58
|
+
`http://localhost:${process.env.API_PORT}/events?currentscreenonly=true`,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (data.events[0].text !== "Accept") {
|
|
62
|
+
await axios.post(`http://localhost:${process.env.API_PORT}/button/right`, {
|
|
63
|
+
action: "press-and-release",
|
|
64
|
+
});
|
|
65
|
+
onSpeculosConfirmation(e);
|
|
66
|
+
} else {
|
|
67
|
+
await axios.post(`http://localhost:${process.env.API_PORT}/button/both`, {
|
|
68
|
+
action: "press-and-release",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return checkSpeculosLogs().then(transport => {
|
|
75
|
+
return {
|
|
76
|
+
transport,
|
|
77
|
+
onSignerConfirmation: onSpeculosConfirmation,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const killSpeculos = async () => {
|
|
83
|
+
console.log("Stopping speculos...");
|
|
84
|
+
await compose.down({
|
|
85
|
+
cwd,
|
|
86
|
+
log: true,
|
|
87
|
+
env: process.env,
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
["exit", "SIGINT", "SIGQUIT", "SIGTERM", "SIGUSR1", "SIGUSR2", "uncaughtException"].map(e =>
|
|
92
|
+
process.on(e, async () => {
|
|
93
|
+
await killSpeculos();
|
|
94
|
+
}),
|
|
95
|
+
);
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"declarationMap": true,
|
|
6
|
+
"module": "commonjs",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"downlevelIteration": true,
|
|
9
|
+
"lib": ["es2020", "dom"],
|
|
10
|
+
"outDir": "lib",
|
|
11
|
+
"typeRoots": ["./types", "./node_modules/@types"],
|
|
12
|
+
"types": ["node", "jest"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
}
|