@alephium/ledger-app 0.6.0 → 0.6.1-rc.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/ledger-app.d.ts +2 -3
- package/dist/src/ledger-app.js +24 -14
- package/dist/src/merkle.d.ts +1 -1
- package/dist/src/merkle.js +3 -3
- package/dist/src/serde.d.ts +0 -1
- package/dist/src/serde.js +6 -6
- package/dist/src/tx-encoder.d.ts +0 -1
- package/dist/src/tx-encoder.js +4 -5
- package/dist/test/merkle.test.js +6 -0
- package/dist/test/utils.d.ts +3 -1
- package/dist/test/utils.js +71 -36
- package/dist/test/wallet.test.js +10 -31
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/eslint.config.js +17 -0
- package/package.json +24 -28
- package/src/ledger-app.ts +8 -9
- package/src/tx-encoder.ts +1 -1
- package/test/merkle.test.ts +7 -1
- package/test/utils.ts +58 -23
- package/test/wallet.test.ts +6 -35
- package/tsconfig.json +0 -1
- package/.eslintignore +0 -2
- package/.eslintrc.json +0 -16
package/dist/src/ledger-app.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { Account, KeyType } from '@alephium/web3';
|
|
1
|
+
import { GroupedAccount, KeyType } from '@alephium/web3';
|
|
3
2
|
import Transport from '@ledgerhq/hw-transport';
|
|
4
3
|
export declare const CLA = 128;
|
|
5
4
|
export declare enum INS {
|
|
@@ -15,7 +14,7 @@ export declare class AlephiumApp {
|
|
|
15
14
|
constructor(transport: Transport);
|
|
16
15
|
close(): Promise<void>;
|
|
17
16
|
getVersion(): Promise<string>;
|
|
18
|
-
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [
|
|
17
|
+
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [GroupedAccount, number]>;
|
|
19
18
|
signHash(path: string, hash: Buffer): Promise<string>;
|
|
20
19
|
signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string>;
|
|
21
20
|
}
|
package/dist/src/ledger-app.js
CHANGED
|
@@ -15,23 +15,31 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.AlephiumApp = exports.HASH_LEN = exports.GROUP_NUM = exports.INS = exports.CLA = void 0;
|
|
27
37
|
const web3_1 = require("@alephium/web3");
|
|
28
38
|
const hw_transport_1 = require("@ledgerhq/hw-transport");
|
|
29
39
|
const serde = __importStar(require("./serde"));
|
|
30
|
-
const elliptic_1 = require("elliptic");
|
|
31
40
|
const types_1 = require("./types");
|
|
32
41
|
const tx_encoder_1 = require("./tx-encoder");
|
|
33
42
|
const merkle_1 = require("./merkle");
|
|
34
|
-
const ec = new elliptic_1.ec('secp256k1');
|
|
35
43
|
exports.CLA = 0x80;
|
|
36
44
|
var INS;
|
|
37
45
|
(function (INS) {
|
|
@@ -39,7 +47,7 @@ var INS;
|
|
|
39
47
|
INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
|
|
40
48
|
INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
|
|
41
49
|
INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
|
|
42
|
-
})(INS
|
|
50
|
+
})(INS || (exports.INS = INS = {}));
|
|
43
51
|
exports.GROUP_NUM = 4;
|
|
44
52
|
exports.HASH_LEN = 32;
|
|
45
53
|
class AlephiumApp {
|
|
@@ -58,18 +66,20 @@ class AlephiumApp {
|
|
|
58
66
|
if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
|
|
59
67
|
throw Error(`Invalid targetGroup: ${targetGroup}`);
|
|
60
68
|
}
|
|
61
|
-
if (keyType
|
|
62
|
-
throw Error(
|
|
69
|
+
if (keyType !== undefined && keyType !== 'default') {
|
|
70
|
+
throw Error(`Unsupported key type: ${keyType}`);
|
|
63
71
|
}
|
|
64
72
|
const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
|
|
65
73
|
const p2 = targetGroup === undefined ? 0x00 : targetGroup;
|
|
66
74
|
const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
|
|
67
75
|
const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
|
|
68
|
-
const
|
|
76
|
+
const prefix = (response[64] & 1) === 1 ? '03' : '02';
|
|
77
|
+
const publicKey = prefix + response.slice(1, 33).toString('hex');
|
|
69
78
|
const address = (0, web3_1.addressFromPublicKey)(publicKey);
|
|
70
79
|
const group = (0, web3_1.groupOfAddress)(address);
|
|
71
80
|
const hdIndex = response.slice(65, 69).readUInt32BE(0);
|
|
72
|
-
|
|
81
|
+
const resolvedKeyType = 'default';
|
|
82
|
+
return [{ publicKey, address, group, keyType: resolvedKeyType }, hdIndex];
|
|
73
83
|
}
|
|
74
84
|
async signHash(path, hash) {
|
|
75
85
|
if (hash.length !== exports.HASH_LEN) {
|
package/dist/src/merkle.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ export declare function generateProofs(): {
|
|
|
5
5
|
proofs: Record<string, string>;
|
|
6
6
|
root: string;
|
|
7
7
|
};
|
|
8
|
-
export declare const tokenMerkleRoot: Uint8Array
|
|
8
|
+
export declare const tokenMerkleRoot: Uint8Array<ArrayBufferLike>;
|
|
9
9
|
export declare const tokenMerkleProofs: Record<string, string>;
|
package/dist/src/merkle.js
CHANGED
|
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.tokenMerkleProofs = exports.tokenMerkleRoot = exports.
|
|
6
|
+
exports.tokenMerkleProofs = exports.tokenMerkleRoot = exports.merkleTokens = void 0;
|
|
7
|
+
exports.hashPair = hashPair;
|
|
8
|
+
exports.generateProofs = generateProofs;
|
|
7
9
|
const web3_1 = require("@alephium/web3");
|
|
8
10
|
const token_json_1 = __importDefault(require("../merkle-tree/token.json"));
|
|
9
11
|
const proofs_json_1 = __importDefault(require("../merkle-tree/proofs.json"));
|
|
@@ -20,7 +22,6 @@ exports.merkleTokens = token_json_1.default.tokens.map((token) => {
|
|
|
20
22
|
function hashPair(a, b) {
|
|
21
23
|
return (0, blakejs_1.blake2b)(Buffer.concat([a, b].sort(Buffer.compare)), undefined, 32);
|
|
22
24
|
}
|
|
23
|
-
exports.hashPair = hashPair;
|
|
24
25
|
function generateMerkleTree(tokens) {
|
|
25
26
|
let level = tokens.map((token) => (0, blakejs_1.blake2b)((0, serde_1.serializeSingleTokenMetadata)(token), undefined, 32));
|
|
26
27
|
const tree = [];
|
|
@@ -54,6 +55,5 @@ function generateProofs() {
|
|
|
54
55
|
console.log('root', tree[tree.length - 1].map((hash) => (0, web3_1.binToHex)(hash)).join(''));
|
|
55
56
|
return { proofs, root: (0, web3_1.binToHex)(tree[tree.length - 1][0]) };
|
|
56
57
|
}
|
|
57
|
-
exports.generateProofs = generateProofs;
|
|
58
58
|
exports.tokenMerkleRoot = (0, web3_1.hexToBinUnsafe)('b3380866c595544781e9da0ccd79399de8878abfb0bf40545b57a287387d419d');
|
|
59
59
|
exports.tokenMerkleProofs = proofs_json_1.default;
|
package/dist/src/serde.d.ts
CHANGED
package/dist/src/serde.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.FALSE = exports.TRUE = void 0;
|
|
4
|
+
exports.splitPath = splitPath;
|
|
5
|
+
exports.serializePath = serializePath;
|
|
6
|
+
exports.checkTokenMetadata = checkTokenMetadata;
|
|
7
|
+
exports.serializeSingleTokenMetadata = serializeSingleTokenMetadata;
|
|
8
|
+
exports.serializeTokenMetadata = serializeTokenMetadata;
|
|
4
9
|
const web3_1 = require("@alephium/web3");
|
|
5
10
|
const types_1 = require("./types");
|
|
6
11
|
exports.TRUE = 0x10;
|
|
@@ -21,7 +26,6 @@ function splitPath(path) {
|
|
|
21
26
|
});
|
|
22
27
|
return result;
|
|
23
28
|
}
|
|
24
|
-
exports.splitPath = splitPath;
|
|
25
29
|
function serializePath(path) {
|
|
26
30
|
const nodes = splitPath(path);
|
|
27
31
|
if (nodes.length != 5) {
|
|
@@ -31,7 +35,6 @@ function serializePath(path) {
|
|
|
31
35
|
nodes.forEach((element, index) => buffer.writeUInt32BE(element, 4 * index));
|
|
32
36
|
return buffer;
|
|
33
37
|
}
|
|
34
|
-
exports.serializePath = serializePath;
|
|
35
38
|
function symbolToBytes(symbol) {
|
|
36
39
|
const buffer = Buffer.alloc(types_1.MAX_TOKEN_SYMBOL_LENGTH, 0);
|
|
37
40
|
for (let i = 0; i < symbol.length; i++) {
|
|
@@ -56,7 +59,6 @@ function checkTokenMetadata(tokens) {
|
|
|
56
59
|
throw new Error(`The token size exceeds maximum size`);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
exports.checkTokenMetadata = checkTokenMetadata;
|
|
60
62
|
function serializeSingleTokenMetadata(metadata) {
|
|
61
63
|
const symbolBytes = symbolToBytes(metadata.symbol);
|
|
62
64
|
const buffer = Buffer.concat([
|
|
@@ -70,10 +72,8 @@ function serializeSingleTokenMetadata(metadata) {
|
|
|
70
72
|
}
|
|
71
73
|
return buffer;
|
|
72
74
|
}
|
|
73
|
-
exports.serializeSingleTokenMetadata = serializeSingleTokenMetadata;
|
|
74
75
|
function serializeTokenMetadata(tokens) {
|
|
75
76
|
checkTokenMetadata(tokens);
|
|
76
77
|
const array = tokens.map((metadata) => serializeSingleTokenMetadata(metadata));
|
|
77
78
|
return Buffer.concat([Buffer.from([array.length]), ...array]);
|
|
78
79
|
}
|
|
79
|
-
exports.serializeTokenMetadata = serializeTokenMetadata;
|
package/dist/src/tx-encoder.d.ts
CHANGED
package/dist/src/tx-encoder.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.encodeTokenMetadata = encodeTokenMetadata;
|
|
4
|
+
exports.encodeProofLength = encodeProofLength;
|
|
5
|
+
exports.encodeUnsignedTx = encodeUnsignedTx;
|
|
6
|
+
exports.assert = assert;
|
|
4
7
|
const merkle_1 = require("./merkle");
|
|
5
8
|
const serde_1 = require("./serde");
|
|
6
9
|
const types_1 = require("./types");
|
|
@@ -25,7 +28,6 @@ function encodeTokenMetadata(tokenMetadata) {
|
|
|
25
28
|
return frames;
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
exports.encodeTokenMetadata = encodeTokenMetadata;
|
|
29
31
|
function encodeTokenAndProof(tokenMetadata, firstFramePrefix) {
|
|
30
32
|
const proof = merkle_1.tokenMerkleProofs[tokenMetadata.tokenId];
|
|
31
33
|
if (proof === undefined)
|
|
@@ -56,7 +58,6 @@ function encodeProofLength(length) {
|
|
|
56
58
|
buffer.writeUint16BE(length);
|
|
57
59
|
return buffer;
|
|
58
60
|
}
|
|
59
|
-
exports.encodeProofLength = encodeProofLength;
|
|
60
61
|
function encodeUnsignedTx(path, unsignedTx) {
|
|
61
62
|
const encodedPath = (0, serde_1.serializePath)(path);
|
|
62
63
|
const firstFrameTxLength = types_1.MAX_PAYLOAD_SIZE - 20;
|
|
@@ -74,9 +75,7 @@ function encodeUnsignedTx(path, unsignedTx) {
|
|
|
74
75
|
}
|
|
75
76
|
return frames;
|
|
76
77
|
}
|
|
77
|
-
exports.encodeUnsignedTx = encodeUnsignedTx;
|
|
78
78
|
function assert(condition, msg) {
|
|
79
79
|
if (!condition)
|
|
80
80
|
throw Error(msg);
|
|
81
81
|
}
|
|
82
|
-
exports.assert = assert;
|
package/dist/test/merkle.test.js
CHANGED
|
@@ -3,7 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const merkle_1 = require("../src/merkle");
|
|
4
4
|
const serde_1 = require("../src/serde");
|
|
5
5
|
const blakejs_1 = require("blakejs");
|
|
6
|
+
const web3_1 = require("@alephium/web3");
|
|
6
7
|
describe('Merkle', () => {
|
|
8
|
+
it('should generate the correct proofs', () => {
|
|
9
|
+
const { proofs, root } = (0, merkle_1.generateProofs)();
|
|
10
|
+
expect(JSON.stringify(proofs)).toBe(JSON.stringify(merkle_1.tokenMerkleProofs));
|
|
11
|
+
expect(root).toBe((0, web3_1.binToHex)(merkle_1.tokenMerkleRoot));
|
|
12
|
+
});
|
|
7
13
|
it('should verify proofs', () => {
|
|
8
14
|
for (const token of merkle_1.merkleTokens) {
|
|
9
15
|
const proof = merkle_1.tokenMerkleProofs[token.tokenId];
|
package/dist/test/utils.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export declare function staxFlexApproveOnce(): Promise<void>;
|
|
|
12
12
|
export declare function approveTx(outputs: OutputType[], hasExternalInputs?: boolean): Promise<void>;
|
|
13
13
|
export declare function approveHash(): Promise<void>;
|
|
14
14
|
export declare function approveAddress(): Promise<void>;
|
|
15
|
+
export declare function isStaxOrFlex(): boolean;
|
|
15
16
|
export declare function isNanos(): boolean;
|
|
16
|
-
export declare function skipBlindSigningWarning(): void
|
|
17
|
+
export declare function skipBlindSigningWarning(): Promise<void>;
|
|
18
|
+
export declare function staxFlexAcceptRisk(): Promise<void>;
|
|
17
19
|
export declare function enableBlindSigning(): Promise<void>;
|
|
18
20
|
export declare function getRandomInt(min: number, max: number): number;
|
|
19
21
|
export declare function needToAutoApprove(): boolean;
|
package/dist/test/utils.js
CHANGED
|
@@ -3,14 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.OutputType = void 0;
|
|
7
|
+
exports.staxFlexApproveOnce = staxFlexApproveOnce;
|
|
8
|
+
exports.approveTx = approveTx;
|
|
9
|
+
exports.approveHash = approveHash;
|
|
10
|
+
exports.approveAddress = approveAddress;
|
|
11
|
+
exports.isStaxOrFlex = isStaxOrFlex;
|
|
12
|
+
exports.isNanos = isNanos;
|
|
13
|
+
exports.skipBlindSigningWarning = skipBlindSigningWarning;
|
|
14
|
+
exports.staxFlexAcceptRisk = staxFlexAcceptRisk;
|
|
15
|
+
exports.enableBlindSigning = enableBlindSigning;
|
|
16
|
+
exports.getRandomInt = getRandomInt;
|
|
17
|
+
exports.needToAutoApprove = needToAutoApprove;
|
|
18
|
+
exports.createTransport = createTransport;
|
|
7
19
|
const hw_transport_node_speculos_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos"));
|
|
8
|
-
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
20
|
const web3_1 = require("@alephium/web3");
|
|
10
21
|
const hw_transport_node_hid_1 = __importDefault(require("@ledgerhq/hw-transport-node-hid"));
|
|
11
22
|
async function pressButton(button) {
|
|
12
23
|
await (0, web3_1.sleep)(1000);
|
|
13
|
-
return (
|
|
24
|
+
return fetch(`http://localhost:25000/button/${button}`, {
|
|
14
25
|
method: 'POST',
|
|
15
26
|
body: JSON.stringify({ action: 'press-and-release' })
|
|
16
27
|
});
|
|
@@ -34,7 +45,7 @@ var OutputType;
|
|
|
34
45
|
OutputType[OutputType["Token"] = 4] = "Token";
|
|
35
46
|
OutputType[OutputType["BaseAndToken"] = 5] = "BaseAndToken";
|
|
36
47
|
OutputType[OutputType["MultisigAndToken"] = 6] = "MultisigAndToken";
|
|
37
|
-
})(OutputType
|
|
48
|
+
})(OutputType || (exports.OutputType = OutputType = {}));
|
|
38
49
|
const NanosClickTable = new Map([
|
|
39
50
|
[OutputType.Base, 5],
|
|
40
51
|
[OutputType.Multisig, 10],
|
|
@@ -52,11 +63,18 @@ const NanospClickTable = new Map([
|
|
|
52
63
|
[OutputType.MultisigAndToken, 8],
|
|
53
64
|
]);
|
|
54
65
|
const StaxClickTable = new Map([
|
|
55
|
-
[OutputType.Base,
|
|
56
|
-
[OutputType.Multisig,
|
|
57
|
-
[OutputType.Token,
|
|
58
|
-
[OutputType.BaseAndToken,
|
|
59
|
-
[OutputType.MultisigAndToken,
|
|
66
|
+
[OutputType.Base, 1],
|
|
67
|
+
[OutputType.Multisig, 2],
|
|
68
|
+
[OutputType.Token, 2],
|
|
69
|
+
[OutputType.BaseAndToken, 2],
|
|
70
|
+
[OutputType.MultisigAndToken, 2],
|
|
71
|
+
]);
|
|
72
|
+
const FlexClickTable = new Map([
|
|
73
|
+
[OutputType.Base, 1],
|
|
74
|
+
[OutputType.Multisig, 2],
|
|
75
|
+
[OutputType.Token, 2],
|
|
76
|
+
[OutputType.BaseAndToken, 2],
|
|
77
|
+
[OutputType.MultisigAndToken, 3],
|
|
60
78
|
]);
|
|
61
79
|
function getOutputClickSize(outputType) {
|
|
62
80
|
const model = getModel();
|
|
@@ -64,8 +82,8 @@ function getOutputClickSize(outputType) {
|
|
|
64
82
|
case 'nanos': return NanosClickTable.get(outputType);
|
|
65
83
|
case 'nanosp':
|
|
66
84
|
case 'nanox': return NanospClickTable.get(outputType);
|
|
67
|
-
case 'stax':
|
|
68
|
-
case 'flex': return
|
|
85
|
+
case 'stax': return StaxClickTable.get(outputType);
|
|
86
|
+
case 'flex': return FlexClickTable.get(outputType);
|
|
69
87
|
default: throw new Error(`Unknown model ${model}`);
|
|
70
88
|
}
|
|
71
89
|
}
|
|
@@ -81,29 +99,40 @@ async function click(outputs, hasExternalInputs) {
|
|
|
81
99
|
}
|
|
82
100
|
const STAX_CONTINUE_POSITION = { x: 342, y: 606 };
|
|
83
101
|
const STAX_APPROVE_POSITION = { x: 200, y: 515 };
|
|
84
|
-
const STAX_REJECT_POSITION = { x: 36, y: 606 };
|
|
85
102
|
const STAX_SETTINGS_POSITION = { x: 342, y: 55 };
|
|
86
103
|
const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 };
|
|
104
|
+
const STAX_GO_TO_SETTINGS = { x: 36, y: 606 };
|
|
105
|
+
const STAX_ACCEPT_RISK_POSITION = { x: 36, y: 606 };
|
|
87
106
|
const FLEX_CONTINUE_POSITION = { x: 430, y: 550 };
|
|
88
107
|
const FLEX_APPROVE_POSITION = { x: 240, y: 435 };
|
|
89
|
-
const FLEX_REJECT_POSITION = { x: 55, y: 530 };
|
|
90
108
|
const FLEX_SETTINGS_POSITION = { x: 405, y: 75 };
|
|
91
109
|
const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 };
|
|
110
|
+
const FLEX_GO_TO_SETTINGS = { x: 55, y: 530 };
|
|
111
|
+
const FLEX_ACCEPT_RISK_POSITION = { x: 55, y: 530 };
|
|
92
112
|
async function touchPosition(pos) {
|
|
93
113
|
await (0, web3_1.sleep)(1000);
|
|
94
|
-
return (
|
|
114
|
+
return fetch(`http://localhost:25000/finger`, {
|
|
95
115
|
method: 'POST',
|
|
96
116
|
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y })
|
|
97
117
|
});
|
|
98
118
|
}
|
|
99
|
-
async function
|
|
119
|
+
async function longPress(pos) {
|
|
120
|
+
await (0, web3_1.sleep)(1000);
|
|
121
|
+
return fetch(`http://localhost:25000/finger`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y, delay: 3 })
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function _touch(times, approve = false) {
|
|
100
127
|
const model = getModel();
|
|
101
128
|
const continuePos = model === 'stax' ? STAX_CONTINUE_POSITION : FLEX_CONTINUE_POSITION;
|
|
102
129
|
for (let i = 0; i < times; i += 1) {
|
|
103
130
|
await touchPosition(continuePos);
|
|
104
131
|
}
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
if (approve) {
|
|
133
|
+
const approvePos = model === 'stax' ? STAX_APPROVE_POSITION : FLEX_APPROVE_POSITION;
|
|
134
|
+
await longPress(approvePos);
|
|
135
|
+
}
|
|
107
136
|
}
|
|
108
137
|
async function staxFlexApproveOnce() {
|
|
109
138
|
if (getModel() === 'stax') {
|
|
@@ -113,16 +142,17 @@ async function staxFlexApproveOnce() {
|
|
|
113
142
|
await touchPosition(FLEX_APPROVE_POSITION);
|
|
114
143
|
}
|
|
115
144
|
}
|
|
116
|
-
exports.staxFlexApproveOnce = staxFlexApproveOnce;
|
|
117
145
|
async function touch(outputs, hasExternalInputs) {
|
|
118
|
-
await (0, web3_1.sleep)(
|
|
146
|
+
await (0, web3_1.sleep)(3000);
|
|
119
147
|
if (hasExternalInputs) {
|
|
120
148
|
await staxFlexApproveOnce();
|
|
121
149
|
}
|
|
150
|
+
_touch(1); // the first review page
|
|
151
|
+
await (0, web3_1.sleep)(1000);
|
|
122
152
|
for (let index = 0; index < outputs.length; index += 1) {
|
|
123
153
|
await _touch(getOutputClickSize(outputs[index]));
|
|
124
154
|
}
|
|
125
|
-
await _touch(
|
|
155
|
+
await _touch(1, true); // fees
|
|
126
156
|
}
|
|
127
157
|
async function approveTx(outputs, hasExternalInputs = false) {
|
|
128
158
|
if (!needToAutoApprove())
|
|
@@ -131,7 +161,7 @@ async function approveTx(outputs, hasExternalInputs = false) {
|
|
|
131
161
|
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs;
|
|
132
162
|
if (isSelfTransfer) {
|
|
133
163
|
if (isStaxOrFlex()) {
|
|
134
|
-
await _touch(2);
|
|
164
|
+
await _touch(2, true);
|
|
135
165
|
}
|
|
136
166
|
else {
|
|
137
167
|
await clickAndApprove(2);
|
|
@@ -145,12 +175,11 @@ async function approveTx(outputs, hasExternalInputs = false) {
|
|
|
145
175
|
await click(outputs, hasExternalInputs);
|
|
146
176
|
}
|
|
147
177
|
}
|
|
148
|
-
exports.approveTx = approveTx;
|
|
149
178
|
async function approveHash() {
|
|
150
179
|
if (!needToAutoApprove())
|
|
151
180
|
return;
|
|
152
181
|
if (isStaxOrFlex()) {
|
|
153
|
-
return await _touch(
|
|
182
|
+
return await _touch(2, true);
|
|
154
183
|
}
|
|
155
184
|
if (getModel() === 'nanos') {
|
|
156
185
|
await clickAndApprove(5);
|
|
@@ -159,12 +188,13 @@ async function approveHash() {
|
|
|
159
188
|
await clickAndApprove(3);
|
|
160
189
|
}
|
|
161
190
|
}
|
|
162
|
-
exports.approveHash = approveHash;
|
|
163
191
|
async function approveAddress() {
|
|
164
192
|
if (!needToAutoApprove())
|
|
165
193
|
return;
|
|
166
194
|
if (isStaxOrFlex()) {
|
|
167
|
-
|
|
195
|
+
await _touch(1);
|
|
196
|
+
await staxFlexApproveOnce();
|
|
197
|
+
return;
|
|
168
198
|
}
|
|
169
199
|
if (getModel() === 'nanos') {
|
|
170
200
|
await clickAndApprove(4);
|
|
@@ -173,26 +203,35 @@ async function approveAddress() {
|
|
|
173
203
|
await clickAndApprove(2);
|
|
174
204
|
}
|
|
175
205
|
}
|
|
176
|
-
exports.approveAddress = approveAddress;
|
|
177
206
|
function isStaxOrFlex() {
|
|
178
207
|
return !getModel().startsWith('nano');
|
|
179
208
|
}
|
|
180
209
|
function isNanos() {
|
|
181
210
|
return getModel() === 'nanos';
|
|
182
211
|
}
|
|
183
|
-
|
|
184
|
-
function skipBlindSigningWarning() {
|
|
212
|
+
async function skipBlindSigningWarning() {
|
|
185
213
|
if (!needToAutoApprove())
|
|
186
214
|
return;
|
|
187
215
|
if (isStaxOrFlex()) {
|
|
188
|
-
|
|
189
|
-
|
|
216
|
+
await (0, web3_1.sleep)(3000);
|
|
217
|
+
const goToSettings = getModel() === 'stax' ? STAX_GO_TO_SETTINGS : FLEX_GO_TO_SETTINGS;
|
|
218
|
+
await touchPosition(goToSettings);
|
|
190
219
|
}
|
|
191
220
|
else {
|
|
192
|
-
clickAndApprove(3);
|
|
221
|
+
await clickAndApprove(3);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function staxFlexAcceptRisk() {
|
|
225
|
+
if (!needToAutoApprove())
|
|
226
|
+
return;
|
|
227
|
+
await (0, web3_1.sleep)(3000);
|
|
228
|
+
if (getModel() === 'stax') {
|
|
229
|
+
await touchPosition(STAX_ACCEPT_RISK_POSITION);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
await touchPosition(FLEX_ACCEPT_RISK_POSITION);
|
|
193
233
|
}
|
|
194
234
|
}
|
|
195
|
-
exports.skipBlindSigningWarning = skipBlindSigningWarning;
|
|
196
235
|
async function enableBlindSigning() {
|
|
197
236
|
if (!needToAutoApprove())
|
|
198
237
|
return;
|
|
@@ -208,13 +247,11 @@ async function enableBlindSigning() {
|
|
|
208
247
|
await clickAndApprove(2);
|
|
209
248
|
}
|
|
210
249
|
}
|
|
211
|
-
exports.enableBlindSigning = enableBlindSigning;
|
|
212
250
|
function getRandomInt(min, max) {
|
|
213
251
|
min = Math.ceil(min);
|
|
214
252
|
max = Math.floor(max);
|
|
215
253
|
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
|
|
216
254
|
}
|
|
217
|
-
exports.getRandomInt = getRandomInt;
|
|
218
255
|
function needToAutoApprove() {
|
|
219
256
|
switch (process.env.BACKEND) {
|
|
220
257
|
case "speculos": return true;
|
|
@@ -222,7 +259,6 @@ function needToAutoApprove() {
|
|
|
222
259
|
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
223
260
|
}
|
|
224
261
|
}
|
|
225
|
-
exports.needToAutoApprove = needToAutoApprove;
|
|
226
262
|
const ApduPort = 9999;
|
|
227
263
|
async function createTransport() {
|
|
228
264
|
switch (process.env.BACKEND) {
|
|
@@ -231,4 +267,3 @@ async function createTransport() {
|
|
|
231
267
|
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
232
268
|
}
|
|
233
269
|
}
|
|
234
|
-
exports.createTransport = createTransport;
|
package/dist/test/wallet.test.js
CHANGED
|
@@ -209,32 +209,6 @@ describe('ledger wallet', () => {
|
|
|
209
209
|
expect(token.amount).toEqual('1111111111111111111111111');
|
|
210
210
|
await app.close();
|
|
211
211
|
}, 120000);
|
|
212
|
-
async function genTokensAndDestinations(fromAddress, toAddress, mintAmount, transferAmount) {
|
|
213
|
-
const tokens = [];
|
|
214
|
-
const tokenSymbol = 'TestTokenABC';
|
|
215
|
-
const destinations = [];
|
|
216
|
-
for (let i = 0; i < 5; i += 1) {
|
|
217
|
-
const tokenInfo = await (0, web3_test_1.mintToken)(fromAddress, mintAmount);
|
|
218
|
-
const tokenMetadata = {
|
|
219
|
-
version: 0,
|
|
220
|
-
tokenId: tokenInfo.contractId,
|
|
221
|
-
symbol: tokenSymbol.slice(0, tokenSymbol.length - i),
|
|
222
|
-
decimals: 18 - i
|
|
223
|
-
};
|
|
224
|
-
tokens.push(tokenMetadata);
|
|
225
|
-
destinations.push({
|
|
226
|
-
address: toAddress,
|
|
227
|
-
attoAlphAmount: web3_1.DUST_AMOUNT.toString(),
|
|
228
|
-
tokens: [
|
|
229
|
-
{
|
|
230
|
-
id: tokenMetadata.tokenId,
|
|
231
|
-
amount: transferAmount.toString()
|
|
232
|
-
}
|
|
233
|
-
]
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return { tokens, destinations };
|
|
237
|
-
}
|
|
238
212
|
it('should transfer tokens with proof', async () => {
|
|
239
213
|
const transport = await (0, utils_1.createTransport)();
|
|
240
214
|
const app = new ledger_app_1.AlephiumApp(transport);
|
|
@@ -242,10 +216,10 @@ describe('ledger wallet', () => {
|
|
|
242
216
|
await transferToAddress(testAccount.address);
|
|
243
217
|
const newAccount = await (0, web3_test_1.getSigner)();
|
|
244
218
|
const selectedTokens = [
|
|
245
|
-
merkle_1.merkleTokens[5],
|
|
246
|
-
merkle_1.merkleTokens[6],
|
|
247
|
-
merkle_1.merkleTokens[8],
|
|
248
|
-
merkle_1.merkleTokens[11],
|
|
219
|
+
merkle_1.merkleTokens[5], // decimals is 0
|
|
220
|
+
merkle_1.merkleTokens[6], // decimals is 18
|
|
221
|
+
merkle_1.merkleTokens[8], // decimals is 9
|
|
222
|
+
merkle_1.merkleTokens[11], // decimals is 8
|
|
249
223
|
merkle_1.merkleTokens[13], // decimals is 6
|
|
250
224
|
];
|
|
251
225
|
const outputs = selectedTokens.map((token, index) => {
|
|
@@ -479,7 +453,12 @@ describe('ledger wallet', () => {
|
|
|
479
453
|
await expect(app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))).rejects.toThrow();
|
|
480
454
|
await (0, utils_1.enableBlindSigning)();
|
|
481
455
|
if ((0, utils_1.needToAutoApprove)()) {
|
|
482
|
-
|
|
456
|
+
if ((0, utils_1.isStaxOrFlex)()) {
|
|
457
|
+
(0, utils_1.staxFlexAcceptRisk)().then(() => (0, utils_1.approveTx)([]));
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
(0, utils_1.approveTx)([]);
|
|
461
|
+
}
|
|
483
462
|
}
|
|
484
463
|
else {
|
|
485
464
|
// waiting for blind signing setting to be enabled
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/index.ts","../src/ledger-app.ts","../src/merkle.ts","../src/serde.test.ts","../src/serde.ts","../src/tx-encoder.ts","../src/types.ts","../test/merkle.test.ts","../test/tx-encoder.test.ts","../test/utils.ts","../test/wallet.test.ts"],"version":"5.8.3"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const tseslint = require('typescript-eslint')
|
|
2
|
+
const eslintConfigPrettier = require('eslint-config-prettier')
|
|
3
|
+
|
|
4
|
+
module.exports = tseslint.config(
|
|
5
|
+
{
|
|
6
|
+
ignores: ['**/dist/', '**/templates/', '**/coverage/', 'eslint.config.js']
|
|
7
|
+
},
|
|
8
|
+
...tseslint.configs.recommended,
|
|
9
|
+
eslintConfigPrettier,
|
|
10
|
+
{
|
|
11
|
+
languageOptions: {
|
|
12
|
+
parserOptions: {
|
|
13
|
+
project: 'tsconfig.json'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
)
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alephium/ledger-app",
|
|
3
|
-
"version": "0.6.0",
|
|
3
|
+
"version": "0.6.1-rc.0",
|
|
4
4
|
"license": "GPL",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
5
6
|
"types": "dist/src/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/alephium/ledger-alephium"
|
|
9
|
+
},
|
|
6
10
|
"exports": {
|
|
7
11
|
".": "./dist/src/index.js"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
|
-
"build": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"lint": "eslint . --ext ts",
|
|
14
|
-
"lint:fix": "eslint . --fix --ext ts",
|
|
14
|
+
"build": "rm -rf dist && tsc --build .",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"lint:fix": "eslint . --fix",
|
|
15
17
|
"test": "BACKEND=speculos jest -i --config ./jest-config.json",
|
|
16
18
|
"speculos-test": "BACKEND=speculos jest -i --config ./jest-config.json",
|
|
17
|
-
"device-test": "BACKEND=device jest -i --config ./jest-config.json"
|
|
18
|
-
"pub": "npm run build && npm publish --access public"
|
|
19
|
+
"device-test": "BACKEND=device jest -i --config ./jest-config.json"
|
|
19
20
|
},
|
|
20
21
|
"prettier": {
|
|
21
22
|
"printWidth": 120,
|
|
@@ -27,32 +28,27 @@
|
|
|
27
28
|
"trailingComma": "none"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@alephium/web3": "
|
|
31
|
-
"@ledgerhq/hw-transport": "6.
|
|
31
|
+
"@alephium/web3": "3.0.0-test.9",
|
|
32
|
+
"@ledgerhq/hw-transport": "6.34.1",
|
|
32
33
|
"blakejs": "^1.2.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@alephium/cli": "
|
|
36
|
-
"@alephium/web3-test": "
|
|
37
|
-
"@alephium/web3-wallet": "
|
|
38
|
-
"@ledgerhq/hw-transport-node-hid": "6.
|
|
39
|
-
"@ledgerhq/hw-transport-node-speculos": "6.
|
|
40
|
-
"@types/
|
|
41
|
-
"@types/jest": "^27.5.1",
|
|
36
|
+
"@alephium/cli": "3.0.0-test.9",
|
|
37
|
+
"@alephium/web3-test": "3.0.0-test.9",
|
|
38
|
+
"@alephium/web3-wallet": "3.0.0-test.9",
|
|
39
|
+
"@ledgerhq/hw-transport-node-hid": "6.32.1",
|
|
40
|
+
"@ledgerhq/hw-transport-node-speculos": "6.33.1",
|
|
41
|
+
"@types/jest": "^29.5.0",
|
|
42
42
|
"@types/node": "^20.8.10",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"eslint": "^
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"jest": "^28.1.0",
|
|
49
|
-
"node-fetch": "^2.6.7",
|
|
50
|
-
"ts-jest": "^28.0.2",
|
|
43
|
+
"eslint": "^9.0.0",
|
|
44
|
+
"eslint-config-prettier": "^10.0.0",
|
|
45
|
+
"typescript-eslint": "^8.0.0",
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"ts-jest": "^29.1.0",
|
|
51
48
|
"ts-node": "^10.7.0",
|
|
52
|
-
"typescript": "
|
|
49
|
+
"typescript": "~5.8.0"
|
|
53
50
|
},
|
|
54
51
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
56
|
-
"npm": ">=7.0.0"
|
|
52
|
+
"node": ">=18.0.0"
|
|
57
53
|
}
|
|
58
54
|
}
|
package/src/ledger-app.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GroupedAccount, GroupedKeyType, KeyType, addressFromPublicKey, binToHex, codec, encodeHexSignature, groupOfAddress } from '@alephium/web3'
|
|
2
2
|
import Transport, { StatusCodes } from '@ledgerhq/hw-transport'
|
|
3
3
|
import * as serde from './serde'
|
|
4
|
-
import { ec as EC } from 'elliptic'
|
|
5
4
|
import { MAX_TOKEN_SIZE, MAX_TOKEN_SYMBOL_LENGTH, TokenMetadata } from './types'
|
|
6
5
|
import { encodeTokenMetadata, encodeUnsignedTx } from './tx-encoder'
|
|
7
6
|
import { merkleTokens } from './merkle'
|
|
8
7
|
|
|
9
|
-
const ec = new EC('secp256k1')
|
|
10
|
-
|
|
11
8
|
export const CLA = 0x80
|
|
12
9
|
export enum INS {
|
|
13
10
|
GET_VERSION = 0x00,
|
|
@@ -36,25 +33,27 @@ export class AlephiumApp {
|
|
|
36
33
|
return `${response[0]}.${response[1]}.${response[2]}`
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [
|
|
36
|
+
async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [GroupedAccount, number]> {
|
|
40
37
|
if ((targetGroup ?? 0) >= GROUP_NUM) {
|
|
41
38
|
throw Error(`Invalid targetGroup: ${targetGroup}`)
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
if (keyType
|
|
45
|
-
throw Error(
|
|
41
|
+
if (keyType !== undefined && keyType !== 'default') {
|
|
42
|
+
throw Error(`Unsupported key type: ${keyType}`)
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
const p1 = targetGroup === undefined ? 0x00 : GROUP_NUM
|
|
49
46
|
const p2 = targetGroup === undefined ? 0x00 : targetGroup
|
|
50
47
|
const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
|
|
51
48
|
const response = await this.transport.send(CLA, INS.GET_PUBLIC_KEY, p1, p2, payload)
|
|
52
|
-
const
|
|
49
|
+
const prefix = (response[64] & 1) === 1 ? '03' : '02'
|
|
50
|
+
const publicKey = prefix + response.slice(1, 33).toString('hex')
|
|
53
51
|
const address = addressFromPublicKey(publicKey)
|
|
54
52
|
const group = groupOfAddress(address)
|
|
55
53
|
const hdIndex = response.slice(65, 69).readUInt32BE(0)
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
const resolvedKeyType: GroupedKeyType = 'default'
|
|
56
|
+
return [{ publicKey, address, group, keyType: resolvedKeyType }, hdIndex] as const
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
async signHash(path: string, hash: Buffer): Promise<string> {
|
package/src/tx-encoder.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { tokenMerkleProofs } from "./merkle"
|
|
2
|
-
import {
|
|
2
|
+
import { serializePath, serializeSingleTokenMetadata } from "./serde"
|
|
3
3
|
import { MAX_PAYLOAD_SIZE, TokenMetadata } from "./types"
|
|
4
4
|
|
|
5
5
|
export interface Frame {
|
package/test/merkle.test.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { hashPair, merkleTokens, tokenMerkleProofs, tokenMerkleRoot } from '../src/merkle'
|
|
1
|
+
import { generateProofs, hashPair, merkleTokens, tokenMerkleProofs, tokenMerkleRoot } from '../src/merkle'
|
|
2
2
|
import { serializeSingleTokenMetadata } from '../src/serde'
|
|
3
3
|
import { blake2b } from 'blakejs'
|
|
4
4
|
import { binToHex } from '@alephium/web3'
|
|
5
5
|
|
|
6
6
|
describe('Merkle', () => {
|
|
7
|
+
it('should generate the correct proofs', () => {
|
|
8
|
+
const { proofs, root } = generateProofs()
|
|
9
|
+
expect(JSON.stringify(proofs)).toBe(JSON.stringify(tokenMerkleProofs))
|
|
10
|
+
expect(root).toBe(binToHex(tokenMerkleRoot))
|
|
11
|
+
})
|
|
12
|
+
|
|
7
13
|
it('should verify proofs', () => {
|
|
8
14
|
for (const token of merkleTokens) {
|
|
9
15
|
const proof = tokenMerkleProofs[token.tokenId]
|
package/test/utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import SpeculosTransport from '@ledgerhq/hw-transport-node-speculos'
|
|
2
|
-
import fetch from 'node-fetch'
|
|
3
2
|
import { sleep } from '@alephium/web3'
|
|
4
3
|
import Transport from '@ledgerhq/hw-transport'
|
|
5
4
|
import NodeTransport from '@ledgerhq/hw-transport-node-hid'
|
|
@@ -53,11 +52,19 @@ const NanospClickTable = new Map([
|
|
|
53
52
|
])
|
|
54
53
|
|
|
55
54
|
const StaxClickTable = new Map([
|
|
56
|
-
[OutputType.Base,
|
|
57
|
-
[OutputType.Multisig,
|
|
58
|
-
[OutputType.Token,
|
|
59
|
-
[OutputType.BaseAndToken,
|
|
60
|
-
[OutputType.MultisigAndToken,
|
|
55
|
+
[OutputType.Base, 1],
|
|
56
|
+
[OutputType.Multisig, 2],
|
|
57
|
+
[OutputType.Token, 2],
|
|
58
|
+
[OutputType.BaseAndToken, 2],
|
|
59
|
+
[OutputType.MultisigAndToken, 2],
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const FlexClickTable = new Map([
|
|
63
|
+
[OutputType.Base, 1],
|
|
64
|
+
[OutputType.Multisig, 2],
|
|
65
|
+
[OutputType.Token, 2],
|
|
66
|
+
[OutputType.BaseAndToken, 2],
|
|
67
|
+
[OutputType.MultisigAndToken, 3],
|
|
61
68
|
])
|
|
62
69
|
|
|
63
70
|
function getOutputClickSize(outputType: OutputType) {
|
|
@@ -66,8 +73,8 @@ function getOutputClickSize(outputType: OutputType) {
|
|
|
66
73
|
case 'nanos': return NanosClickTable.get(outputType)!
|
|
67
74
|
case 'nanosp':
|
|
68
75
|
case 'nanox': return NanospClickTable.get(outputType)!
|
|
69
|
-
case 'stax':
|
|
70
|
-
case 'flex': return
|
|
76
|
+
case 'stax': return StaxClickTable.get(outputType)!
|
|
77
|
+
case 'flex': return FlexClickTable.get(outputType)!
|
|
71
78
|
default: throw new Error(`Unknown model ${model}`)
|
|
72
79
|
}
|
|
73
80
|
}
|
|
@@ -92,15 +99,17 @@ interface Position {
|
|
|
92
99
|
|
|
93
100
|
const STAX_CONTINUE_POSITION = { x: 342, y: 606 }
|
|
94
101
|
const STAX_APPROVE_POSITION = { x: 200, y: 515 }
|
|
95
|
-
const STAX_REJECT_POSITION = { x: 36, y: 606 }
|
|
96
102
|
const STAX_SETTINGS_POSITION = { x: 342, y: 55 }
|
|
97
103
|
const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 }
|
|
104
|
+
const STAX_GO_TO_SETTINGS = { x: 36, y: 606 }
|
|
105
|
+
const STAX_ACCEPT_RISK_POSITION = { x: 36, y: 606 }
|
|
98
106
|
|
|
99
107
|
const FLEX_CONTINUE_POSITION = { x: 430, y: 550 }
|
|
100
108
|
const FLEX_APPROVE_POSITION = { x: 240, y: 435 }
|
|
101
|
-
const FLEX_REJECT_POSITION = { x: 55, y: 530 }
|
|
102
109
|
const FLEX_SETTINGS_POSITION = { x: 405, y: 75 }
|
|
103
110
|
const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 }
|
|
111
|
+
const FLEX_GO_TO_SETTINGS = { x: 55, y: 530 }
|
|
112
|
+
const FLEX_ACCEPT_RISK_POSITION = { x: 55, y: 530 }
|
|
104
113
|
|
|
105
114
|
async function touchPosition(pos: Position) {
|
|
106
115
|
await sleep(1000)
|
|
@@ -110,14 +119,24 @@ async function touchPosition(pos: Position) {
|
|
|
110
119
|
})
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
async function
|
|
122
|
+
async function longPress(pos: Position) {
|
|
123
|
+
await sleep(1000)
|
|
124
|
+
return fetch(`http://localhost:25000/finger`, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y, delay: 3 })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function _touch(times: number, approve: boolean = false) {
|
|
114
131
|
const model = getModel()
|
|
115
132
|
const continuePos = model === 'stax' ? STAX_CONTINUE_POSITION : FLEX_CONTINUE_POSITION
|
|
116
133
|
for (let i = 0; i < times; i += 1) {
|
|
117
134
|
await touchPosition(continuePos)
|
|
118
135
|
}
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
if (approve) {
|
|
137
|
+
const approvePos = model === 'stax' ? STAX_APPROVE_POSITION : FLEX_APPROVE_POSITION
|
|
138
|
+
await longPress(approvePos)
|
|
139
|
+
}
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
export async function staxFlexApproveOnce() {
|
|
@@ -129,16 +148,19 @@ export async function staxFlexApproveOnce() {
|
|
|
129
148
|
}
|
|
130
149
|
|
|
131
150
|
async function touch(outputs: OutputType[], hasExternalInputs: boolean) {
|
|
132
|
-
await sleep(
|
|
151
|
+
await sleep(3000);
|
|
133
152
|
if (hasExternalInputs) {
|
|
134
153
|
await staxFlexApproveOnce()
|
|
135
154
|
}
|
|
136
155
|
|
|
156
|
+
_touch(1) // the first review page
|
|
157
|
+
await sleep(1000)
|
|
158
|
+
|
|
137
159
|
for (let index = 0; index < outputs.length; index += 1) {
|
|
138
160
|
await _touch(getOutputClickSize(outputs[index]))
|
|
139
161
|
}
|
|
140
162
|
|
|
141
|
-
await _touch(
|
|
163
|
+
await _touch(1, true) // fees
|
|
142
164
|
}
|
|
143
165
|
|
|
144
166
|
export async function approveTx(outputs: OutputType[], hasExternalInputs: boolean = false) {
|
|
@@ -147,7 +169,7 @@ export async function approveTx(outputs: OutputType[], hasExternalInputs: boolea
|
|
|
147
169
|
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs
|
|
148
170
|
if (isSelfTransfer) {
|
|
149
171
|
if (isStaxOrFlex()) {
|
|
150
|
-
await _touch(2)
|
|
172
|
+
await _touch(2, true)
|
|
151
173
|
} else {
|
|
152
174
|
await clickAndApprove(2)
|
|
153
175
|
}
|
|
@@ -164,7 +186,7 @@ export async function approveTx(outputs: OutputType[], hasExternalInputs: boolea
|
|
|
164
186
|
export async function approveHash() {
|
|
165
187
|
if (!needToAutoApprove()) return
|
|
166
188
|
if (isStaxOrFlex()) {
|
|
167
|
-
return await _touch(
|
|
189
|
+
return await _touch(2, true)
|
|
168
190
|
}
|
|
169
191
|
if (getModel() === 'nanos') {
|
|
170
192
|
await clickAndApprove(5)
|
|
@@ -176,7 +198,9 @@ export async function approveHash() {
|
|
|
176
198
|
export async function approveAddress() {
|
|
177
199
|
if (!needToAutoApprove()) return
|
|
178
200
|
if (isStaxOrFlex()) {
|
|
179
|
-
|
|
201
|
+
await _touch(1)
|
|
202
|
+
await staxFlexApproveOnce()
|
|
203
|
+
return
|
|
180
204
|
}
|
|
181
205
|
if (getModel() === 'nanos') {
|
|
182
206
|
await clickAndApprove(4)
|
|
@@ -185,7 +209,7 @@ export async function approveAddress() {
|
|
|
185
209
|
}
|
|
186
210
|
}
|
|
187
211
|
|
|
188
|
-
function isStaxOrFlex(): boolean {
|
|
212
|
+
export function isStaxOrFlex(): boolean {
|
|
189
213
|
return !getModel().startsWith('nano')
|
|
190
214
|
}
|
|
191
215
|
|
|
@@ -193,13 +217,24 @@ export function isNanos(): boolean {
|
|
|
193
217
|
return getModel() === 'nanos'
|
|
194
218
|
}
|
|
195
219
|
|
|
196
|
-
export function skipBlindSigningWarning() {
|
|
220
|
+
export async function skipBlindSigningWarning() {
|
|
197
221
|
if (!needToAutoApprove()) return
|
|
198
222
|
if (isStaxOrFlex()) {
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
await sleep(3000)
|
|
224
|
+
const goToSettings = getModel() === 'stax' ? STAX_GO_TO_SETTINGS : FLEX_GO_TO_SETTINGS
|
|
225
|
+
await touchPosition(goToSettings)
|
|
226
|
+
} else {
|
|
227
|
+
await clickAndApprove(3)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function staxFlexAcceptRisk() {
|
|
232
|
+
if (!needToAutoApprove()) return
|
|
233
|
+
await sleep(3000)
|
|
234
|
+
if (getModel() === 'stax') {
|
|
235
|
+
await touchPosition(STAX_ACCEPT_RISK_POSITION)
|
|
201
236
|
} else {
|
|
202
|
-
|
|
237
|
+
await touchPosition(FLEX_ACCEPT_RISK_POSITION)
|
|
203
238
|
}
|
|
204
239
|
}
|
|
205
240
|
|
package/test/wallet.test.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { ALPH_TOKEN_ID, Address, DUST_AMOUNT, NodeProvider, ONE_ALPH, binToHex,
|
|
|
3
3
|
import { getSigner, mintToken, transfer } from '@alephium/web3-test'
|
|
4
4
|
import { PrivateKeyWallet } from '@alephium/web3-wallet'
|
|
5
5
|
import blake from 'blakejs'
|
|
6
|
-
import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, isNanos, needToAutoApprove, OutputType, skipBlindSigningWarning,
|
|
7
|
-
import { TokenMetadata } from '../src/types'
|
|
6
|
+
import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, isNanos, isStaxOrFlex, needToAutoApprove, OutputType, skipBlindSigningWarning, staxFlexAcceptRisk } from './utils'
|
|
8
7
|
import { randomBytes } from 'crypto'
|
|
9
8
|
import { merkleTokens, tokenMerkleProofs } from '../src/merkle'
|
|
10
9
|
|
|
@@ -241,38 +240,6 @@ describe('ledger wallet', () => {
|
|
|
241
240
|
await app.close()
|
|
242
241
|
}, 120000)
|
|
243
242
|
|
|
244
|
-
async function genTokensAndDestinations(
|
|
245
|
-
fromAddress: string,
|
|
246
|
-
toAddress: string,
|
|
247
|
-
mintAmount: bigint,
|
|
248
|
-
transferAmount: bigint
|
|
249
|
-
) {
|
|
250
|
-
const tokens: TokenMetadata[] = []
|
|
251
|
-
const tokenSymbol = 'TestTokenABC'
|
|
252
|
-
const destinations: node.Destination[] = []
|
|
253
|
-
for (let i = 0; i < 5; i += 1) {
|
|
254
|
-
const tokenInfo = await mintToken(fromAddress, mintAmount);
|
|
255
|
-
const tokenMetadata: TokenMetadata = {
|
|
256
|
-
version: 0,
|
|
257
|
-
tokenId: tokenInfo.contractId,
|
|
258
|
-
symbol: tokenSymbol.slice(0, tokenSymbol.length - i),
|
|
259
|
-
decimals: 18 - i
|
|
260
|
-
}
|
|
261
|
-
tokens.push(tokenMetadata)
|
|
262
|
-
destinations.push({
|
|
263
|
-
address: toAddress,
|
|
264
|
-
attoAlphAmount: DUST_AMOUNT.toString(),
|
|
265
|
-
tokens: [
|
|
266
|
-
{
|
|
267
|
-
id: tokenMetadata.tokenId,
|
|
268
|
-
amount: transferAmount.toString()
|
|
269
|
-
}
|
|
270
|
-
]
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
return { tokens, destinations }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
243
|
it('should transfer tokens with proof', async () => {
|
|
277
244
|
const transport = await createTransport()
|
|
278
245
|
const app = new AlephiumApp(transport)
|
|
@@ -547,7 +514,11 @@ describe('ledger wallet', () => {
|
|
|
547
514
|
|
|
548
515
|
await enableBlindSigning()
|
|
549
516
|
if (needToAutoApprove()) {
|
|
550
|
-
|
|
517
|
+
if (isStaxOrFlex()) {
|
|
518
|
+
staxFlexAcceptRisk().then(() => approveTx([]))
|
|
519
|
+
} else {
|
|
520
|
+
approveTx([])
|
|
521
|
+
}
|
|
551
522
|
} else {
|
|
552
523
|
// waiting for blind signing setting to be enabled
|
|
553
524
|
await sleep(20000)
|
package/tsconfig.json
CHANGED
package/.eslintignore
DELETED
package/.eslintrc.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": [
|
|
3
|
-
"prettier",
|
|
4
|
-
"plugin:prettier/recommended",
|
|
5
|
-
"plugin:@typescript-eslint/recommended"
|
|
6
|
-
],
|
|
7
|
-
"rules": {
|
|
8
|
-
"header/header": ["off"]
|
|
9
|
-
},
|
|
10
|
-
"parserOptions": {
|
|
11
|
-
"project": "tsconfig.json",
|
|
12
|
-
"ecmaVersion": 2020,
|
|
13
|
-
"sourceType": "module"
|
|
14
|
-
},
|
|
15
|
-
"parser": "@typescript-eslint/parser"
|
|
16
|
-
}
|