@alephium/ledger-app 0.3.0 → 0.5.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/dist/src/index.d.ts +6 -3
- package/dist/src/index.js +22 -8
- package/dist/src/serde.d.ts +10 -0
- package/dist/src/serde.js +48 -1
- package/dist/src/serde.test.js +66 -0
- package/dist/test/utils.d.ts +17 -0
- package/dist/test/utils.js +225 -0
- package/dist/test/{speculos.test.js → wallet.test.js} +214 -174
- package/package.json +7 -7
- package/src/index.ts +30 -8
- package/src/serde.ts +59 -0
- package/test/utils.ts +233 -0
- package/test/{speculos.test.ts → wallet.test.ts} +248 -167
- package/dist/test/release.test.js +0 -132
- package/dist/test/speculos.test.d.ts +0 -1
- package/test/release.test.ts +0 -152
- /package/dist/test/{release.test.d.ts → wallet.test.d.ts} +0 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Account, KeyType } from '@alephium/web3';
|
|
3
3
|
import Transport from '@ledgerhq/hw-transport';
|
|
4
|
+
import * as serde from './serde';
|
|
4
5
|
export declare const CLA = 128;
|
|
5
6
|
export declare enum INS {
|
|
6
7
|
GET_VERSION = 0,
|
|
7
8
|
GET_PUBLIC_KEY = 1,
|
|
8
|
-
|
|
9
|
+
SIGN_HASH = 2,
|
|
10
|
+
SIGN_TX = 3
|
|
9
11
|
}
|
|
10
12
|
export declare const GROUP_NUM = 4;
|
|
11
13
|
export declare const HASH_LEN = 32;
|
|
@@ -14,6 +16,7 @@ export default class AlephiumApp {
|
|
|
14
16
|
constructor(transport: Transport);
|
|
15
17
|
close(): Promise<void>;
|
|
16
18
|
getVersion(): Promise<string>;
|
|
17
|
-
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType): Promise<readonly [Account, number]>;
|
|
18
|
-
|
|
19
|
+
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
|
|
20
|
+
signHash(path: string, hash: Buffer): Promise<string>;
|
|
21
|
+
signUnsignedTx(path: string, unsignedTx: Buffer, tokenMetadata?: serde.TokenMetadata[]): Promise<string>;
|
|
19
22
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -34,10 +34,13 @@ var INS;
|
|
|
34
34
|
(function (INS) {
|
|
35
35
|
INS[INS["GET_VERSION"] = 0] = "GET_VERSION";
|
|
36
36
|
INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
|
|
37
|
-
INS[INS["
|
|
37
|
+
INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
|
|
38
|
+
INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
|
|
38
39
|
})(INS = exports.INS || (exports.INS = {}));
|
|
39
40
|
exports.GROUP_NUM = 4;
|
|
40
41
|
exports.HASH_LEN = 32;
|
|
42
|
+
// The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
|
|
43
|
+
const MAX_PAYLOAD_SIZE = 255;
|
|
41
44
|
class AlephiumApp {
|
|
42
45
|
constructor(transport) {
|
|
43
46
|
this.transport = transport;
|
|
@@ -50,8 +53,7 @@ class AlephiumApp {
|
|
|
50
53
|
console.log(`response ${response.length} - ${response.toString('hex')}`);
|
|
51
54
|
return `${response[0]}.${response[1]}.${response[2]}`;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
async getAccount(startPath, targetGroup, keyType) {
|
|
56
|
+
async getAccount(startPath, targetGroup, keyType, display = false) {
|
|
55
57
|
if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
|
|
56
58
|
throw Error(`Invalid targetGroup: ${targetGroup}`);
|
|
57
59
|
}
|
|
@@ -60,24 +62,36 @@ class AlephiumApp {
|
|
|
60
62
|
}
|
|
61
63
|
const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
|
|
62
64
|
const p2 = targetGroup === undefined ? 0x00 : targetGroup;
|
|
63
|
-
const
|
|
65
|
+
const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
|
|
66
|
+
const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
|
|
64
67
|
const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex');
|
|
65
68
|
const address = (0, web3_1.addressFromPublicKey)(publicKey);
|
|
66
69
|
const group = (0, web3_1.groupOfAddress)(address);
|
|
67
70
|
const hdIndex = response.slice(65, 69).readUInt32BE(0);
|
|
68
71
|
return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex];
|
|
69
72
|
}
|
|
70
|
-
async
|
|
73
|
+
async signHash(path, hash) {
|
|
74
|
+
if (hash.length !== exports.HASH_LEN) {
|
|
75
|
+
throw new Error('Invalid hash length');
|
|
76
|
+
}
|
|
77
|
+
const data = Buffer.concat([serde.serializePath(path), hash]);
|
|
78
|
+
console.log(`data ${data.length}`);
|
|
79
|
+
const response = await this.transport.send(exports.CLA, INS.SIGN_HASH, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
|
|
80
|
+
console.log(`response ${response.length} - ${response.toString('hex')}`);
|
|
81
|
+
return decodeSignature(response);
|
|
82
|
+
}
|
|
83
|
+
async signUnsignedTx(path, unsignedTx, tokenMetadata = []) {
|
|
71
84
|
console.log(`unsigned tx size: ${unsignedTx.length}`);
|
|
72
85
|
const encodedPath = serde.serializePath(path);
|
|
73
|
-
const
|
|
86
|
+
const encodedTokenMetadata = serde.serializeTokenMetadata(tokenMetadata);
|
|
87
|
+
const firstFrameTxLength = MAX_PAYLOAD_SIZE - 20 - encodedTokenMetadata.length;
|
|
74
88
|
const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length);
|
|
75
|
-
const data = Buffer.concat([encodedPath, txData]);
|
|
89
|
+
const data = Buffer.concat([encodedPath, encodedTokenMetadata, txData]);
|
|
76
90
|
let response = await this.transport.send(exports.CLA, INS.SIGN_TX, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
|
|
77
91
|
if (unsignedTx.length <= firstFrameTxLength) {
|
|
78
92
|
return decodeSignature(response);
|
|
79
93
|
}
|
|
80
|
-
const frameLength =
|
|
94
|
+
const frameLength = MAX_PAYLOAD_SIZE;
|
|
81
95
|
let fromIndex = firstFrameTxLength;
|
|
82
96
|
while (fromIndex < unsignedTx.length) {
|
|
83
97
|
const remain = unsignedTx.length - fromIndex;
|
package/dist/src/serde.d.ts
CHANGED
|
@@ -3,3 +3,13 @@ export declare const TRUE = 16;
|
|
|
3
3
|
export declare const FALSE = 0;
|
|
4
4
|
export declare function splitPath(path: string): number[];
|
|
5
5
|
export declare function serializePath(path: string): Buffer;
|
|
6
|
+
export declare const MAX_TOKEN_SIZE = 5;
|
|
7
|
+
export declare const MAX_TOKEN_SYMBOL_LENGTH = 12;
|
|
8
|
+
export declare const TOKEN_METADATA_SIZE = 46;
|
|
9
|
+
export interface TokenMetadata {
|
|
10
|
+
version: number;
|
|
11
|
+
tokenId: string;
|
|
12
|
+
symbol: string;
|
|
13
|
+
decimals: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function serializeTokenMetadata(tokens: TokenMetadata[]): Buffer;
|
package/dist/src/serde.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
|
|
3
|
+
exports.serializeTokenMetadata = exports.TOKEN_METADATA_SIZE = exports.MAX_TOKEN_SYMBOL_LENGTH = exports.MAX_TOKEN_SIZE = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
|
|
4
|
+
const web3_1 = require("@alephium/web3");
|
|
4
5
|
exports.TRUE = 0x10;
|
|
5
6
|
exports.FALSE = 0x00;
|
|
6
7
|
function splitPath(path) {
|
|
@@ -30,3 +31,49 @@ function serializePath(path) {
|
|
|
30
31
|
return buffer;
|
|
31
32
|
}
|
|
32
33
|
exports.serializePath = serializePath;
|
|
34
|
+
exports.MAX_TOKEN_SIZE = 5;
|
|
35
|
+
exports.MAX_TOKEN_SYMBOL_LENGTH = 12;
|
|
36
|
+
exports.TOKEN_METADATA_SIZE = 46;
|
|
37
|
+
function symbolToBytes(symbol) {
|
|
38
|
+
const buffer = Buffer.alloc(exports.MAX_TOKEN_SYMBOL_LENGTH, 0);
|
|
39
|
+
for (let i = 0; i < symbol.length; i++) {
|
|
40
|
+
buffer[i] = symbol.charCodeAt(i) & 0xFF;
|
|
41
|
+
}
|
|
42
|
+
return buffer;
|
|
43
|
+
}
|
|
44
|
+
function check(tokens) {
|
|
45
|
+
const hasDuplicate = tokens.some((token, index) => index !== tokens.findIndex((t) => t.tokenId === token.tokenId));
|
|
46
|
+
if (hasDuplicate) {
|
|
47
|
+
throw new Error(`There are duplicate tokens`);
|
|
48
|
+
}
|
|
49
|
+
tokens.forEach((token) => {
|
|
50
|
+
if (!((0, web3_1.isHexString)(token.tokenId) && token.tokenId.length === 64)) {
|
|
51
|
+
throw new Error(`Invalid token id: ${token.tokenId}`);
|
|
52
|
+
}
|
|
53
|
+
if (token.symbol.length > exports.MAX_TOKEN_SYMBOL_LENGTH) {
|
|
54
|
+
throw new Error(`The token symbol is too long: ${token.symbol}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (tokens.length > exports.MAX_TOKEN_SIZE) {
|
|
58
|
+
throw new Error(`The token size exceeds maximum size`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function serializeTokenMetadata(tokens) {
|
|
62
|
+
check(tokens);
|
|
63
|
+
const array = tokens
|
|
64
|
+
.map((metadata) => {
|
|
65
|
+
const symbolBytes = symbolToBytes(metadata.symbol);
|
|
66
|
+
const buffer = Buffer.concat([
|
|
67
|
+
Buffer.from([metadata.version]),
|
|
68
|
+
Buffer.from(metadata.tokenId, 'hex'),
|
|
69
|
+
symbolBytes,
|
|
70
|
+
Buffer.from([metadata.decimals])
|
|
71
|
+
]);
|
|
72
|
+
if (buffer.length !== exports.TOKEN_METADATA_SIZE) {
|
|
73
|
+
throw new Error(`Invalid token metadata: ${metadata}`);
|
|
74
|
+
}
|
|
75
|
+
return buffer;
|
|
76
|
+
});
|
|
77
|
+
return Buffer.concat([Buffer.from([array.length]), ...array]);
|
|
78
|
+
}
|
|
79
|
+
exports.serializeTokenMetadata = serializeTokenMetadata;
|
package/dist/src/serde.test.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const web3_1 = require("@alephium/web3");
|
|
3
4
|
const serde_1 = require("./serde");
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
4
6
|
describe('serde', () => {
|
|
5
7
|
it('should split path', () => {
|
|
6
8
|
expect((0, serde_1.splitPath)(`m/44'/1234'/0'/0/0`)).toStrictEqual([44 + 0x80000000, 1234 + 0x80000000, 0 + 0x80000000, 0, 0]);
|
|
@@ -10,4 +12,68 @@ describe('serde', () => {
|
|
|
10
12
|
expect((0, serde_1.serializePath)(`m/1'/2'/0'/0/0`)).toStrictEqual(Buffer.from([0x80, 0, 0, 1, 0x80, 0, 0, 2, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
11
13
|
expect((0, serde_1.serializePath)(`m/1'/2'/0'/0/0`)).toStrictEqual(Buffer.from([0x80, 0, 0, 1, 0x80, 0, 0, 2, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
12
14
|
});
|
|
15
|
+
it('should encode token metadata', () => {
|
|
16
|
+
const token0 = {
|
|
17
|
+
version: 0,
|
|
18
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
19
|
+
symbol: 'Token0',
|
|
20
|
+
decimals: 8
|
|
21
|
+
};
|
|
22
|
+
const token1 = {
|
|
23
|
+
version: 1,
|
|
24
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
25
|
+
symbol: 'Token1',
|
|
26
|
+
decimals: 18
|
|
27
|
+
};
|
|
28
|
+
const token2 = {
|
|
29
|
+
version: 2,
|
|
30
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
31
|
+
symbol: 'Token2',
|
|
32
|
+
decimals: 6
|
|
33
|
+
};
|
|
34
|
+
const token3 = {
|
|
35
|
+
version: 3,
|
|
36
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
37
|
+
symbol: 'Token3',
|
|
38
|
+
decimals: 0
|
|
39
|
+
};
|
|
40
|
+
const token4 = {
|
|
41
|
+
version: 4,
|
|
42
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
43
|
+
symbol: 'Token4',
|
|
44
|
+
decimals: 12
|
|
45
|
+
};
|
|
46
|
+
const encodeSymbol = (symbol) => {
|
|
47
|
+
return (0, web3_1.binToHex)(Buffer.from(symbol, 'ascii')).padEnd(serde_1.MAX_TOKEN_SYMBOL_LENGTH * 2, '0');
|
|
48
|
+
};
|
|
49
|
+
expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([]))).toEqual('00');
|
|
50
|
+
expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([token0]))).toEqual('01' + '00' + token0.tokenId + encodeSymbol(token0.symbol) + '08');
|
|
51
|
+
expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([token1]))).toEqual('01' + '01' + token1.tokenId + encodeSymbol(token1.symbol) + '12');
|
|
52
|
+
expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([token0, token1]))).toEqual('02' + '00' + token0.tokenId + encodeSymbol(token0.symbol) + '08' +
|
|
53
|
+
'01' + token1.tokenId + encodeSymbol(token1.symbol) + '12');
|
|
54
|
+
expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([token0, token1, token2, token3, token4]))).toEqual('05' + '00' + token0.tokenId + encodeSymbol(token0.symbol) + '08' +
|
|
55
|
+
'01' + token1.tokenId + encodeSymbol(token1.symbol) + '12' +
|
|
56
|
+
'02' + token2.tokenId + encodeSymbol(token2.symbol) + '06' +
|
|
57
|
+
'03' + token3.tokenId + encodeSymbol(token3.symbol) + '00' +
|
|
58
|
+
'04' + token4.tokenId + encodeSymbol(token4.symbol) + '0c');
|
|
59
|
+
expect(() => (0, serde_1.serializeTokenMetadata)([token0, token1, token0])).toThrow('There are duplicate tokens');
|
|
60
|
+
const token5 = {
|
|
61
|
+
version: 5,
|
|
62
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
63
|
+
symbol: 'Token5',
|
|
64
|
+
decimals: 18
|
|
65
|
+
};
|
|
66
|
+
expect(() => (0, serde_1.serializeTokenMetadata)([token0, token1, token2, token3, token4, token5])).toThrow('The token size exceeds maximum size');
|
|
67
|
+
const invalidToken = {
|
|
68
|
+
...token0,
|
|
69
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(33))
|
|
70
|
+
};
|
|
71
|
+
expect(() => (0, serde_1.serializeTokenMetadata)([token0, invalidToken])).toThrow('Invalid token id');
|
|
72
|
+
const longSymbolToken = {
|
|
73
|
+
...token0,
|
|
74
|
+
tokenId: (0, web3_1.binToHex)((0, crypto_1.randomBytes)(32)),
|
|
75
|
+
symbol: 'LongSymbolToken'
|
|
76
|
+
};
|
|
77
|
+
expect(() => (0, serde_1.serializeTokenMetadata)([token0, longSymbolToken, token1])).toThrow('The token symbol is too long');
|
|
78
|
+
});
|
|
13
79
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Transport from '@ledgerhq/hw-transport';
|
|
2
|
+
export declare enum OutputType {
|
|
3
|
+
Base = 0,
|
|
4
|
+
Multisig = 1,
|
|
5
|
+
Token = 2,
|
|
6
|
+
BaseAndToken = 3,
|
|
7
|
+
MultisigAndToken = 4
|
|
8
|
+
}
|
|
9
|
+
export declare function staxFlexApproveOnce(): Promise<void>;
|
|
10
|
+
export declare function approveTx(outputs: OutputType[], hasExternalInputs?: boolean): Promise<void>;
|
|
11
|
+
export declare function approveHash(): Promise<void>;
|
|
12
|
+
export declare function approveAddress(): Promise<void>;
|
|
13
|
+
export declare function skipBlindSigningWarning(): void;
|
|
14
|
+
export declare function enableBlindSigning(): Promise<void>;
|
|
15
|
+
export declare function getRandomInt(min: number, max: number): number;
|
|
16
|
+
export declare function needToAutoApprove(): boolean;
|
|
17
|
+
export declare function createTransport(): Promise<Transport>;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTransport = exports.needToAutoApprove = exports.getRandomInt = exports.enableBlindSigning = exports.skipBlindSigningWarning = exports.approveAddress = exports.approveHash = exports.approveTx = exports.staxFlexApproveOnce = exports.OutputType = void 0;
|
|
7
|
+
const hw_transport_node_speculos_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos"));
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
const web3_1 = require("@alephium/web3");
|
|
10
|
+
const hw_transport_node_hid_1 = __importDefault(require("@ledgerhq/hw-transport-node-hid"));
|
|
11
|
+
async function pressButton(button) {
|
|
12
|
+
await (0, web3_1.sleep)(1000);
|
|
13
|
+
return (0, node_fetch_1.default)(`http://localhost:25000/button/${button}`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body: JSON.stringify({ action: 'press-and-release' })
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function clickAndApprove(times) {
|
|
19
|
+
for (let i = 0; i < times; i++) {
|
|
20
|
+
await pressButton('right');
|
|
21
|
+
}
|
|
22
|
+
await pressButton('both');
|
|
23
|
+
}
|
|
24
|
+
function getModel() {
|
|
25
|
+
const model = process.env.MODEL;
|
|
26
|
+
return model ? model : 'nanos';
|
|
27
|
+
}
|
|
28
|
+
var OutputType;
|
|
29
|
+
(function (OutputType) {
|
|
30
|
+
OutputType[OutputType["Base"] = 0] = "Base";
|
|
31
|
+
OutputType[OutputType["Multisig"] = 1] = "Multisig";
|
|
32
|
+
OutputType[OutputType["Token"] = 2] = "Token";
|
|
33
|
+
OutputType[OutputType["BaseAndToken"] = 3] = "BaseAndToken";
|
|
34
|
+
OutputType[OutputType["MultisigAndToken"] = 4] = "MultisigAndToken";
|
|
35
|
+
})(OutputType = exports.OutputType || (exports.OutputType = {}));
|
|
36
|
+
const NanosClickTable = new Map([
|
|
37
|
+
[OutputType.Base, 5],
|
|
38
|
+
[OutputType.Multisig, 10],
|
|
39
|
+
[OutputType.Token, 11],
|
|
40
|
+
[OutputType.BaseAndToken, 12],
|
|
41
|
+
[OutputType.MultisigAndToken, 16],
|
|
42
|
+
]);
|
|
43
|
+
const NanospClickTable = new Map([
|
|
44
|
+
[OutputType.Base, 3],
|
|
45
|
+
[OutputType.Multisig, 5],
|
|
46
|
+
[OutputType.Token, 6],
|
|
47
|
+
[OutputType.BaseAndToken, 6],
|
|
48
|
+
[OutputType.MultisigAndToken, 8],
|
|
49
|
+
]);
|
|
50
|
+
const StaxClickTable = new Map([
|
|
51
|
+
[OutputType.Base, 2],
|
|
52
|
+
[OutputType.Multisig, 3],
|
|
53
|
+
[OutputType.Token, 3],
|
|
54
|
+
[OutputType.BaseAndToken, 3],
|
|
55
|
+
[OutputType.MultisigAndToken, 4],
|
|
56
|
+
]);
|
|
57
|
+
function getOutputClickSize(outputType) {
|
|
58
|
+
const model = getModel();
|
|
59
|
+
switch (model) {
|
|
60
|
+
case 'nanos': return NanosClickTable.get(outputType);
|
|
61
|
+
case 'nanosp':
|
|
62
|
+
case 'nanox': return NanospClickTable.get(outputType);
|
|
63
|
+
case 'stax':
|
|
64
|
+
case 'flex': return StaxClickTable.get(outputType);
|
|
65
|
+
default: throw new Error(`Unknown model ${model}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function click(outputs, hasExternalInputs) {
|
|
69
|
+
await (0, web3_1.sleep)(1000);
|
|
70
|
+
if (hasExternalInputs) {
|
|
71
|
+
await clickAndApprove(1);
|
|
72
|
+
}
|
|
73
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
74
|
+
await clickAndApprove(getOutputClickSize(outputs[index]));
|
|
75
|
+
}
|
|
76
|
+
await clickAndApprove(1); // fees
|
|
77
|
+
}
|
|
78
|
+
const STAX_CONTINUE_POSITION = { x: 342, y: 606 };
|
|
79
|
+
const STAX_APPROVE_POSITION = { x: 200, y: 515 };
|
|
80
|
+
const STAX_REJECT_POSITION = { x: 36, y: 606 };
|
|
81
|
+
const STAX_SETTINGS_POSITION = { x: 342, y: 55 };
|
|
82
|
+
const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 };
|
|
83
|
+
const FLEX_CONTINUE_POSITION = { x: 430, y: 550 };
|
|
84
|
+
const FLEX_APPROVE_POSITION = { x: 240, y: 435 };
|
|
85
|
+
const FLEX_REJECT_POSITION = { x: 55, y: 530 };
|
|
86
|
+
const FLEX_SETTINGS_POSITION = { x: 405, y: 75 };
|
|
87
|
+
const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 };
|
|
88
|
+
async function touchPosition(pos) {
|
|
89
|
+
await (0, web3_1.sleep)(1000);
|
|
90
|
+
return (0, node_fetch_1.default)(`http://localhost:25000/finger`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y })
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function _touch(times) {
|
|
96
|
+
const model = getModel();
|
|
97
|
+
const continuePos = model === 'stax' ? STAX_CONTINUE_POSITION : FLEX_CONTINUE_POSITION;
|
|
98
|
+
for (let i = 0; i < times; i += 1) {
|
|
99
|
+
await touchPosition(continuePos);
|
|
100
|
+
}
|
|
101
|
+
const approvePos = model === 'stax' ? STAX_APPROVE_POSITION : FLEX_APPROVE_POSITION;
|
|
102
|
+
await touchPosition(approvePos);
|
|
103
|
+
}
|
|
104
|
+
async function staxFlexApproveOnce() {
|
|
105
|
+
if (getModel() === 'stax') {
|
|
106
|
+
await touchPosition(STAX_APPROVE_POSITION);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
await touchPosition(FLEX_APPROVE_POSITION);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.staxFlexApproveOnce = staxFlexApproveOnce;
|
|
113
|
+
async function touch(outputs, hasExternalInputs) {
|
|
114
|
+
await (0, web3_1.sleep)(1000);
|
|
115
|
+
if (hasExternalInputs) {
|
|
116
|
+
await staxFlexApproveOnce();
|
|
117
|
+
}
|
|
118
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
119
|
+
await _touch(getOutputClickSize(outputs[index]));
|
|
120
|
+
}
|
|
121
|
+
await _touch(2); // fees
|
|
122
|
+
}
|
|
123
|
+
async function approveTx(outputs, hasExternalInputs = false) {
|
|
124
|
+
if (!needToAutoApprove())
|
|
125
|
+
return;
|
|
126
|
+
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs;
|
|
127
|
+
if (isSelfTransfer) {
|
|
128
|
+
if (isStaxOrFlex()) {
|
|
129
|
+
await _touch(2);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
await clickAndApprove(2);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (isStaxOrFlex()) {
|
|
137
|
+
await touch(outputs, hasExternalInputs);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await click(outputs, hasExternalInputs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.approveTx = approveTx;
|
|
144
|
+
async function approveHash() {
|
|
145
|
+
if (!needToAutoApprove())
|
|
146
|
+
return;
|
|
147
|
+
if (isStaxOrFlex()) {
|
|
148
|
+
return await _touch(3);
|
|
149
|
+
}
|
|
150
|
+
if (getModel() === 'nanos') {
|
|
151
|
+
await clickAndApprove(5);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
await clickAndApprove(3);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.approveHash = approveHash;
|
|
158
|
+
async function approveAddress() {
|
|
159
|
+
if (!needToAutoApprove())
|
|
160
|
+
return;
|
|
161
|
+
if (isStaxOrFlex()) {
|
|
162
|
+
return await _touch(2);
|
|
163
|
+
}
|
|
164
|
+
if (getModel() === 'nanos') {
|
|
165
|
+
await clickAndApprove(4);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
await clickAndApprove(2);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.approveAddress = approveAddress;
|
|
172
|
+
function isStaxOrFlex() {
|
|
173
|
+
return !getModel().startsWith('nano');
|
|
174
|
+
}
|
|
175
|
+
function skipBlindSigningWarning() {
|
|
176
|
+
if (!needToAutoApprove())
|
|
177
|
+
return;
|
|
178
|
+
if (isStaxOrFlex()) {
|
|
179
|
+
const rejectPos = getModel() === 'stax' ? STAX_REJECT_POSITION : FLEX_REJECT_POSITION;
|
|
180
|
+
touchPosition(rejectPos);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
clickAndApprove(3);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.skipBlindSigningWarning = skipBlindSigningWarning;
|
|
187
|
+
async function enableBlindSigning() {
|
|
188
|
+
if (!needToAutoApprove())
|
|
189
|
+
return;
|
|
190
|
+
if (isStaxOrFlex()) {
|
|
191
|
+
const model = getModel();
|
|
192
|
+
const settingsPos = model === 'stax' ? STAX_SETTINGS_POSITION : FLEX_SETTINGS_POSITION;
|
|
193
|
+
const blindSettingPos = model === 'stax' ? STAX_BLIND_SETTING_POSITION : FLEX_BLIND_SETTING_POSITION;
|
|
194
|
+
await touchPosition(settingsPos);
|
|
195
|
+
await touchPosition(blindSettingPos);
|
|
196
|
+
await touchPosition(settingsPos);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
await clickAndApprove(2);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.enableBlindSigning = enableBlindSigning;
|
|
203
|
+
function getRandomInt(min, max) {
|
|
204
|
+
min = Math.ceil(min);
|
|
205
|
+
max = Math.floor(max);
|
|
206
|
+
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
|
|
207
|
+
}
|
|
208
|
+
exports.getRandomInt = getRandomInt;
|
|
209
|
+
function needToAutoApprove() {
|
|
210
|
+
switch (process.env.BACKEND) {
|
|
211
|
+
case "speculos": return true;
|
|
212
|
+
case "device": return false;
|
|
213
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.needToAutoApprove = needToAutoApprove;
|
|
217
|
+
const ApduPort = 9999;
|
|
218
|
+
async function createTransport() {
|
|
219
|
+
switch (process.env.BACKEND) {
|
|
220
|
+
case "speculos": return hw_transport_node_speculos_1.default.open({ apduPort: ApduPort });
|
|
221
|
+
case "device": return hw_transport_node_hid_1.default.open('');
|
|
222
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.createTransport = createTransport;
|