@alephium/ledger-app 0.2.1 → 0.4.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 +4 -2
- package/dist/src/index.js +35 -10
- package/dist/test/utils.d.ts +16 -0
- package/dist/test/utils.js +215 -0
- package/dist/test/wallet.test.js +383 -0
- package/docker/devnet.conf +55 -0
- package/docker/docker-compose.yaml +22 -0
- package/package.json +11 -11
- package/src/index.ts +40 -11
- package/test/utils.ts +222 -0
- package/test/wallet.test.ts +412 -0
- package/dist/test/release.test.js +0 -26
- package/dist/test/speculos.test.d.ts +0 -1
- package/dist/test/speculos.test.js +0 -123
- package/test/release.test.ts +0 -28
- package/test/speculos.test.ts +0 -111
- /package/dist/test/{release.test.d.ts → wallet.test.d.ts} +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const src_1 = __importStar(require("../src"));
|
|
30
|
+
const web3_1 = require("@alephium/web3");
|
|
31
|
+
const web3_test_1 = require("@alephium/web3-test");
|
|
32
|
+
const web3_wallet_1 = require("@alephium/web3-wallet");
|
|
33
|
+
const blakejs_1 = __importDefault(require("blakejs"));
|
|
34
|
+
const utils_1 = require("./utils");
|
|
35
|
+
describe('ledger wallet', () => {
|
|
36
|
+
const nodeProvider = new web3_1.NodeProvider("http://127.0.0.1:22973");
|
|
37
|
+
web3_1.web3.setCurrentNodeProvider(nodeProvider);
|
|
38
|
+
let pathIndex;
|
|
39
|
+
let path;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
pathIndex = (0, utils_1.getRandomInt)(0, 1000000);
|
|
42
|
+
path = `m/44'/1234'/0'/0/` + pathIndex;
|
|
43
|
+
});
|
|
44
|
+
async function transferToAddress(address, amount = web3_1.ONE_ALPH * 10n) {
|
|
45
|
+
const balance0 = await getALPHBalance(address);
|
|
46
|
+
const fromAccount = await (0, web3_test_1.getSigner)();
|
|
47
|
+
const transferResult = await (0, web3_test_1.transfer)(fromAccount, address, web3_1.ALPH_TOKEN_ID, amount);
|
|
48
|
+
await (0, web3_1.waitForTxConfirmation)(transferResult.txId, 1, 1000);
|
|
49
|
+
const balance1 = await getALPHBalance(address);
|
|
50
|
+
expect(balance1 - balance0).toEqual(amount);
|
|
51
|
+
}
|
|
52
|
+
async function getALPHBalance(address) {
|
|
53
|
+
const balances = await nodeProvider.addresses.getAddressesAddressBalance(address);
|
|
54
|
+
return BigInt(balances.balance);
|
|
55
|
+
}
|
|
56
|
+
it('should get version', async () => {
|
|
57
|
+
const transport = await (0, utils_1.createTransport)();
|
|
58
|
+
const app = new src_1.default(transport);
|
|
59
|
+
const version = await app.getVersion();
|
|
60
|
+
expect(version).toBe('0.4.0');
|
|
61
|
+
await app.close();
|
|
62
|
+
});
|
|
63
|
+
it('should get public key', async () => {
|
|
64
|
+
const transport = await (0, utils_1.createTransport)();
|
|
65
|
+
const app = new src_1.default(transport);
|
|
66
|
+
const [account, hdIndex] = await app.getAccount(path);
|
|
67
|
+
expect(hdIndex).toBe(pathIndex);
|
|
68
|
+
console.log(account);
|
|
69
|
+
await app.close();
|
|
70
|
+
});
|
|
71
|
+
it('should get public key and confirm address', async () => {
|
|
72
|
+
const transport = await (0, utils_1.createTransport)();
|
|
73
|
+
const app = new src_1.default(transport);
|
|
74
|
+
(0, utils_1.approveAddress)();
|
|
75
|
+
const [account, hdIndex] = await app.getAccount(path, undefined, undefined, true);
|
|
76
|
+
expect(hdIndex).toBe(pathIndex);
|
|
77
|
+
console.log(account);
|
|
78
|
+
await app.close();
|
|
79
|
+
}, 30000);
|
|
80
|
+
it('should get public key for group', async () => {
|
|
81
|
+
const transport = await (0, utils_1.createTransport)();
|
|
82
|
+
const app = new src_1.default(transport);
|
|
83
|
+
for (let group = 0; group < src_1.GROUP_NUM; group++) {
|
|
84
|
+
const [account, hdIndex] = await app.getAccount(path, group);
|
|
85
|
+
expect(hdIndex >= pathIndex).toBe(true);
|
|
86
|
+
expect((0, web3_1.groupOfAddress)(account.address)).toBe(group);
|
|
87
|
+
expect(account.keyType).toBe('default');
|
|
88
|
+
}
|
|
89
|
+
await app.close();
|
|
90
|
+
});
|
|
91
|
+
it('should get public key for group for Schnorr signature', async () => {
|
|
92
|
+
const transport = await (0, utils_1.createTransport)();
|
|
93
|
+
const app = new src_1.default(transport);
|
|
94
|
+
for (let group = 0; group < src_1.GROUP_NUM; group++) {
|
|
95
|
+
await expect(app.getAccount(path, group, 'bip340-schnorr')).rejects.toThrow('BIP340-Schnorr is not supported yet');
|
|
96
|
+
}
|
|
97
|
+
await app.close();
|
|
98
|
+
});
|
|
99
|
+
it('should sign hash', async () => {
|
|
100
|
+
const transport = await (0, utils_1.createTransport)();
|
|
101
|
+
const app = new src_1.default(transport);
|
|
102
|
+
const [account] = await app.getAccount(path);
|
|
103
|
+
console.log(account);
|
|
104
|
+
const hash = Buffer.from(blakejs_1.default.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32));
|
|
105
|
+
(0, utils_1.approveHash)();
|
|
106
|
+
const signature = await app.signHash(path, hash);
|
|
107
|
+
console.log(signature);
|
|
108
|
+
await app.close();
|
|
109
|
+
expect((0, web3_1.transactionVerifySignature)(hash.toString('hex'), account.publicKey, signature)).toBe(true);
|
|
110
|
+
}, 10000);
|
|
111
|
+
it('shoudl transfer alph to one address', async () => {
|
|
112
|
+
const transport = await (0, utils_1.createTransport)();
|
|
113
|
+
const app = new src_1.default(transport);
|
|
114
|
+
const [testAccount] = await app.getAccount(path);
|
|
115
|
+
await transferToAddress(testAccount.address);
|
|
116
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
117
|
+
fromPublicKey: testAccount.publicKey,
|
|
118
|
+
destinations: [
|
|
119
|
+
{
|
|
120
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
121
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 2n).toString(),
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
(0, utils_1.approveTx)([utils_1.OutputType.Base]);
|
|
126
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
127
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
128
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
129
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
130
|
+
signature: signature
|
|
131
|
+
});
|
|
132
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
133
|
+
const balance = await getALPHBalance(testAccount.address);
|
|
134
|
+
expect(balance < (web3_1.ONE_ALPH * 8n)).toEqual(true);
|
|
135
|
+
await app.close();
|
|
136
|
+
}, 120000);
|
|
137
|
+
it('should transfer alph to multiple addresses', async () => {
|
|
138
|
+
const transport = await (0, utils_1.createTransport)();
|
|
139
|
+
const app = new src_1.default(transport);
|
|
140
|
+
const [testAccount] = await app.getAccount(path);
|
|
141
|
+
await transferToAddress(testAccount.address);
|
|
142
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
143
|
+
fromPublicKey: testAccount.publicKey,
|
|
144
|
+
destinations: [
|
|
145
|
+
{
|
|
146
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
147
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 2n).toString(),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
address: '1F1fu6GjuN9yUVRFVcgQKWwiTg8RMzKFv1BZFDwFcfWJq',
|
|
151
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 3n).toString(),
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
(0, utils_1.approveTx)(Array(2).fill(utils_1.OutputType.Base));
|
|
156
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
157
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
158
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
159
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
160
|
+
signature: signature
|
|
161
|
+
});
|
|
162
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
163
|
+
const balance1 = await getALPHBalance(testAccount.address);
|
|
164
|
+
expect(balance1 < (web3_1.ONE_ALPH * 5n)).toEqual(true);
|
|
165
|
+
await app.close();
|
|
166
|
+
}, 120000);
|
|
167
|
+
it('should transfer alph to multisig address', async () => {
|
|
168
|
+
const transport = await (0, utils_1.createTransport)();
|
|
169
|
+
const app = new src_1.default(transport);
|
|
170
|
+
const [testAccount] = await app.getAccount(path);
|
|
171
|
+
await transferToAddress(testAccount.address);
|
|
172
|
+
const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
|
|
173
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
174
|
+
fromPublicKey: testAccount.publicKey,
|
|
175
|
+
destinations: [
|
|
176
|
+
{
|
|
177
|
+
address: multiSigAddress,
|
|
178
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 2n).toString(),
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
(0, utils_1.approveTx)([utils_1.OutputType.Multisig]);
|
|
183
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
184
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
185
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
186
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
187
|
+
signature: signature
|
|
188
|
+
});
|
|
189
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
190
|
+
const balance1 = await getALPHBalance(testAccount.address);
|
|
191
|
+
expect(balance1 < (web3_1.ONE_ALPH * 8n)).toEqual(true);
|
|
192
|
+
await app.close();
|
|
193
|
+
}, 120000);
|
|
194
|
+
it('should transfer token to multisig address', async () => {
|
|
195
|
+
const transport = await (0, utils_1.createTransport)();
|
|
196
|
+
const app = new src_1.default(transport);
|
|
197
|
+
const [testAccount] = await app.getAccount(path);
|
|
198
|
+
await transferToAddress(testAccount.address);
|
|
199
|
+
const tokenInfo = await (0, web3_test_1.mintToken)(testAccount.address, 2222222222222222222222222n);
|
|
200
|
+
const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
|
|
201
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
202
|
+
fromPublicKey: testAccount.publicKey,
|
|
203
|
+
destinations: [
|
|
204
|
+
{
|
|
205
|
+
address: multiSigAddress,
|
|
206
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 5n).toString(),
|
|
207
|
+
tokens: [
|
|
208
|
+
{
|
|
209
|
+
id: tokenInfo.contractId,
|
|
210
|
+
amount: "1111111111111111111111111",
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
});
|
|
216
|
+
(0, utils_1.approveTx)([utils_1.OutputType.MultisigAndToken, utils_1.OutputType.Multisig]);
|
|
217
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
218
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
219
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
220
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
221
|
+
signature: signature
|
|
222
|
+
});
|
|
223
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
224
|
+
const balances = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address);
|
|
225
|
+
const alphBalance = BigInt(balances.balance);
|
|
226
|
+
expect(alphBalance < (web3_1.ONE_ALPH * 5n)).toEqual(true);
|
|
227
|
+
expect(balances.tokenBalances.length).toEqual(1);
|
|
228
|
+
const token = balances.tokenBalances[0];
|
|
229
|
+
expect(token.id).toEqual(tokenInfo.contractId);
|
|
230
|
+
expect(token.amount).toEqual('1111111111111111111111111');
|
|
231
|
+
await app.close();
|
|
232
|
+
}, 120000);
|
|
233
|
+
it('should transfer from multiple inputs', async () => {
|
|
234
|
+
const transport = await (0, utils_1.createTransport)();
|
|
235
|
+
const app = new src_1.default(transport);
|
|
236
|
+
const [testAccount] = await app.getAccount(path);
|
|
237
|
+
for (let i = 0; i < 20; i += 1) {
|
|
238
|
+
await transferToAddress(testAccount.address, web3_1.ONE_ALPH);
|
|
239
|
+
}
|
|
240
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
241
|
+
fromPublicKey: testAccount.publicKey,
|
|
242
|
+
destinations: [
|
|
243
|
+
{
|
|
244
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
245
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 19n).toString(),
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
(0, utils_1.approveTx)([utils_1.OutputType.Base]);
|
|
250
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
251
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
252
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
253
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
254
|
+
signature: signature
|
|
255
|
+
});
|
|
256
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
257
|
+
const balance = await getALPHBalance(testAccount.address);
|
|
258
|
+
expect(balance < (web3_1.ONE_ALPH * 1n)).toEqual(true);
|
|
259
|
+
await app.close();
|
|
260
|
+
}, 120000);
|
|
261
|
+
function getAccount(groupIndex) {
|
|
262
|
+
const useDefaultKeyType = Math.random() >= 0.5;
|
|
263
|
+
if (useDefaultKeyType) {
|
|
264
|
+
const account = web3_wallet_1.PrivateKeyWallet.Random(groupIndex);
|
|
265
|
+
return { account, unlockScript: '00' + account.publicKey };
|
|
266
|
+
}
|
|
267
|
+
const account = web3_wallet_1.PrivateKeyWallet.Random(groupIndex, nodeProvider, 'bip340-schnorr');
|
|
268
|
+
const unlockScript = '02' + `0101000000000458144020${account.publicKey}8685` + '00';
|
|
269
|
+
return { account, unlockScript };
|
|
270
|
+
}
|
|
271
|
+
it('should test external inputs', async () => {
|
|
272
|
+
const transport = await (0, utils_1.createTransport)();
|
|
273
|
+
const app = new src_1.default(transport);
|
|
274
|
+
const [testAccount] = await app.getAccount(path);
|
|
275
|
+
const { account: newAccount, unlockScript: unlockScript0 } = getAccount(testAccount.group);
|
|
276
|
+
for (let i = 0; i < 2; i += 1) {
|
|
277
|
+
await transferToAddress(testAccount.address, web3_1.ONE_ALPH);
|
|
278
|
+
await transferToAddress(newAccount.address, web3_1.ONE_ALPH);
|
|
279
|
+
}
|
|
280
|
+
const utxos0 = await nodeProvider.addresses.getAddressesAddressUtxos(newAccount.address);
|
|
281
|
+
expect(utxos0.utxos.length).toEqual(2);
|
|
282
|
+
const utxos1 = await nodeProvider.addresses.getAddressesAddressUtxos(testAccount.address);
|
|
283
|
+
expect(utxos1.utxos.length).toEqual(2);
|
|
284
|
+
const useSameAsPrevious = Math.random() >= 0.5;
|
|
285
|
+
const inputs0 = utxos0.utxos.map((utxo, index) => {
|
|
286
|
+
const unlockScript = index > 0 && useSameAsPrevious ? '03' : unlockScript0;
|
|
287
|
+
return { outputRef: utxo.ref, unlockScript };
|
|
288
|
+
});
|
|
289
|
+
const unlockScript1 = '00' + testAccount.publicKey;
|
|
290
|
+
const inputs1 = utxos1.utxos.map((utxo, index) => {
|
|
291
|
+
const unlockScript = index > 0 && useSameAsPrevious ? '03' : unlockScript1;
|
|
292
|
+
return { outputRef: utxo.ref, unlockScript };
|
|
293
|
+
});
|
|
294
|
+
const unsignedTx = {
|
|
295
|
+
txId: '',
|
|
296
|
+
version: 0,
|
|
297
|
+
networkId: 4,
|
|
298
|
+
gasAmount: 100000,
|
|
299
|
+
gasPrice: (web3_1.ONE_ALPH / 100000n).toString(),
|
|
300
|
+
inputs: inputs0.concat(inputs1),
|
|
301
|
+
fixedOutputs: [{
|
|
302
|
+
hint: 0,
|
|
303
|
+
key: '',
|
|
304
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 3n).toString(),
|
|
305
|
+
address: newAccount.address,
|
|
306
|
+
tokens: [],
|
|
307
|
+
lockTime: 0,
|
|
308
|
+
message: ''
|
|
309
|
+
}]
|
|
310
|
+
};
|
|
311
|
+
const txBytes = web3_1.codec.unsignedTxCodec.encodeApiUnsignedTx(unsignedTx);
|
|
312
|
+
const signResult0 = await newAccount.signUnsignedTx({
|
|
313
|
+
signerAddress: newAccount.address,
|
|
314
|
+
unsignedTx: (0, web3_1.binToHex)(txBytes)
|
|
315
|
+
});
|
|
316
|
+
(0, utils_1.approveTx)([utils_1.OutputType.Base], true);
|
|
317
|
+
const signature1 = await app.signUnsignedTx(path, Buffer.from(txBytes));
|
|
318
|
+
expect((0, web3_1.transactionVerifySignature)(signResult0.txId, testAccount.publicKey, signature1)).toBe(true);
|
|
319
|
+
const submitResult = await nodeProvider.multisig.postMultisigSubmit({
|
|
320
|
+
unsignedTx: (0, web3_1.binToHex)(txBytes),
|
|
321
|
+
signatures: [signResult0.signature, signature1]
|
|
322
|
+
});
|
|
323
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
324
|
+
const balance = await getALPHBalance(newAccount.address);
|
|
325
|
+
expect(balance).toEqual(web3_1.ONE_ALPH * 3n);
|
|
326
|
+
await app.close();
|
|
327
|
+
}, 120000);
|
|
328
|
+
it('should test self transfer tx', async () => {
|
|
329
|
+
const transport = await (0, utils_1.createTransport)();
|
|
330
|
+
const app = new src_1.default(transport);
|
|
331
|
+
const [testAccount] = await app.getAccount(path);
|
|
332
|
+
await transferToAddress(testAccount.address);
|
|
333
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
334
|
+
fromPublicKey: testAccount.publicKey,
|
|
335
|
+
destinations: [
|
|
336
|
+
{
|
|
337
|
+
address: testAccount.address,
|
|
338
|
+
attoAlphAmount: (web3_1.ONE_ALPH * 2n).toString(),
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
});
|
|
342
|
+
(0, utils_1.approveTx)([]);
|
|
343
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
344
|
+
expect((0, web3_1.transactionVerifySignature)(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true);
|
|
345
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
346
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
347
|
+
signature: signature
|
|
348
|
+
});
|
|
349
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
350
|
+
const balance = await getALPHBalance(testAccount.address);
|
|
351
|
+
expect(balance > (web3_1.ONE_ALPH * 9n)).toEqual(true);
|
|
352
|
+
await app.close();
|
|
353
|
+
}, 12000);
|
|
354
|
+
it('should test script execution tx', async () => {
|
|
355
|
+
const transport = await (0, utils_1.createTransport)();
|
|
356
|
+
const app = new src_1.default(transport);
|
|
357
|
+
const [testAccount] = await app.getAccount(path);
|
|
358
|
+
await transferToAddress(testAccount.address);
|
|
359
|
+
const buildTxResult = await nodeProvider.contracts.postContractsUnsignedTxDeployContract({
|
|
360
|
+
fromPublicKey: testAccount.publicKey,
|
|
361
|
+
bytecode: '00010c010000000002d38d0b3636020000'
|
|
362
|
+
});
|
|
363
|
+
setTimeout(() => (0, utils_1.skipBlindSigningWarning)(), 1000);
|
|
364
|
+
await expect(app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))).rejects.toThrow();
|
|
365
|
+
await (0, utils_1.enableBlindSigning)();
|
|
366
|
+
if ((0, utils_1.needToAutoApprove)()) {
|
|
367
|
+
(0, utils_1.staxFlexApproveOnce)().then(() => (0, utils_1.approveTx)([]));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// waiting for blind signing setting to be enabled
|
|
371
|
+
await (0, web3_1.sleep)(20000);
|
|
372
|
+
}
|
|
373
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'));
|
|
374
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
375
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
376
|
+
signature: signature
|
|
377
|
+
});
|
|
378
|
+
await (0, web3_1.waitForTxConfirmation)(submitResult.txId, 1, 1000);
|
|
379
|
+
const details = await nodeProvider.transactions.getTransactionsDetailsTxid(submitResult.txId);
|
|
380
|
+
expect(details.scriptExecutionOk).toEqual(true);
|
|
381
|
+
await app.close();
|
|
382
|
+
}, 120000);
|
|
383
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Import this mnemonic to have 4'000'000 token allocated for your addresses
|
|
2
|
+
#
|
|
3
|
+
# vault alarm sad mass witness property virus style good flower rice alpha viable evidence run glare pretty scout evil judge enroll refuse another lava
|
|
4
|
+
|
|
5
|
+
alephium.genesis.allocations = [
|
|
6
|
+
{
|
|
7
|
+
address = "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
|
|
8
|
+
amount = 1000000000000000000000000,
|
|
9
|
+
lock-duration = 0 seconds
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
address = "14UAjZ3qcmEVKdTo84Kwf4RprTQi86w2TefnnGFjov9xF",
|
|
13
|
+
amount = 1000000000000000000000000,
|
|
14
|
+
lock-duration = 0 seconds
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
address = "15jjExDyS8q3Wqk9v29PCQ21jDqubDrD8WQdgn6VW2oi4",
|
|
18
|
+
amount = 1000000000000000000000000,
|
|
19
|
+
lock-duration = 0 seconds
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
address = "17cBiTcWhung3WDLuc9ja5Y7BMus5Q7CD9wYBxS1r1P2R",
|
|
23
|
+
amount = 1000000000000000000000000,
|
|
24
|
+
lock-duration = 0 seconds
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
alephium.consensus.num-zeros-at-least-in-hash = 0
|
|
28
|
+
alephium.consensus.mainnet.block-target-time = 10 millis
|
|
29
|
+
alephium.consensus.mainnet.uncle-dependency-gap-time = 0 seconds
|
|
30
|
+
alephium.consensus.rhone.block-target-time = 5 millis
|
|
31
|
+
alephium.consensus.rhone.uncle-dependency-gap-time = 0 seconds
|
|
32
|
+
alephium.network.leman-hard-fork-timestamp = 0
|
|
33
|
+
alephium.network.rhone-hard-fork-timestamp = 0
|
|
34
|
+
|
|
35
|
+
alephium.network.network-id = 4
|
|
36
|
+
alephium.discovery.bootstrap = []
|
|
37
|
+
alephium.wallet.locking-timeout = 99999 minutes
|
|
38
|
+
alephium.mempool.auto-mine-for-dev = true
|
|
39
|
+
alephium.node.event-log.enabled=true
|
|
40
|
+
alephium.node.event-log.index-by-tx-id = true
|
|
41
|
+
alephium.node.event-log.index-by-block-hash = true
|
|
42
|
+
|
|
43
|
+
alephium.network.rest-port = 22973
|
|
44
|
+
alephium.network.ws-port = 21973
|
|
45
|
+
alephium.network.miner-api-port = 20973
|
|
46
|
+
alephium.api.network-interface = "0.0.0.0"
|
|
47
|
+
alephium.api.api-key-enabled = false
|
|
48
|
+
|
|
49
|
+
# arbitrary mining addresses
|
|
50
|
+
alephium.mining.miner-addresses = [
|
|
51
|
+
"1FsroWmeJPBhcPiUr37pWXdojRBe6jdey9uukEXk1TheA",
|
|
52
|
+
"1CQvSXsmM5BMFKguKDPpNUfw1idiut8UifLtT8748JdHc",
|
|
53
|
+
"193maApeJWrz9GFwWCfa982ccLARVE9Y1WgKSJaUs7UAx",
|
|
54
|
+
"16fZKYPCZJv2TP3FArA9FLUQceTS9U8xVnSjxFG9MBKyY"
|
|
55
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
version: "3.3"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
alephium:
|
|
5
|
+
image: alephium/alephium:v3.3.0
|
|
6
|
+
restart: "no"
|
|
7
|
+
ports:
|
|
8
|
+
- 19973:19973/tcp
|
|
9
|
+
- 19973:19973/udp
|
|
10
|
+
- 127.0.0.1:20973:20973
|
|
11
|
+
- 127.0.0.1:21973:21973
|
|
12
|
+
- 127.0.0.1:22973:22973
|
|
13
|
+
security_opt:
|
|
14
|
+
- no-new-privileges:true
|
|
15
|
+
volumes:
|
|
16
|
+
- ./devnet.conf:/alephium-home/.alephium/user.conf
|
|
17
|
+
# - ~/.alephium:/alephium-home/.alephium
|
|
18
|
+
environment:
|
|
19
|
+
ALEPHIUM_FILE_LOG_LEVEL: "DEBUG"
|
|
20
|
+
healthcheck:
|
|
21
|
+
test: ["CMD", "curl", "http://127.0.0.1:22973/infos/self-clique"]
|
|
22
|
+
timeout: 45s
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alephium/ledger-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "GPL",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"clean:windows": "node -e \"if (process.platform === 'win32') process.exit(1)\" || , if exist dist rmdir /Q /S dist",
|
|
13
13
|
"lint": "eslint . --ext ts",
|
|
14
14
|
"lint:fix": "eslint . --fix --ext ts",
|
|
15
|
-
"test": "jest -i --config ./jest-config.json",
|
|
15
|
+
"speculos-test": "BACKEND=speculos jest -i --config ./jest-config.json",
|
|
16
|
+
"device-test": "BACKEND=device jest -i --config ./jest-config.json",
|
|
16
17
|
"pub": "npm run build && npm publish --access public"
|
|
17
18
|
},
|
|
18
19
|
"prettier": {
|
|
@@ -25,19 +26,18 @@
|
|
|
25
26
|
"trailingComma": "none"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@alephium/
|
|
29
|
-
"@
|
|
30
|
-
"@alephium/web3-test": "^0.14.0",
|
|
31
|
-
"@alephium/web3-wallet": "^0.14.0",
|
|
32
|
-
"@ledgerhq/hw-transport": "6.27.10"
|
|
29
|
+
"@alephium/web3": "^1.2.0",
|
|
30
|
+
"@ledgerhq/hw-transport": "6.31.0"
|
|
33
31
|
},
|
|
34
32
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
33
|
+
"@alephium/web3-test": "^1.2.0",
|
|
34
|
+
"@alephium/web3-wallet": "^1.2.0",
|
|
35
|
+
"@alephium/cli": "^1.2.0",
|
|
36
|
+
"@ledgerhq/hw-transport-node-hid": "6.29.1",
|
|
37
|
+
"@ledgerhq/hw-transport-node-speculos": "6.29.0",
|
|
38
38
|
"@types/elliptic": "^6.4.13",
|
|
39
39
|
"@types/jest": "^27.5.1",
|
|
40
|
-
"@types/node": "^
|
|
40
|
+
"@types/node": "^20.8.10",
|
|
41
41
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
|
42
42
|
"@typescript-eslint/parser": "^4.30.0",
|
|
43
43
|
"eslint": "^7.32.0",
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,8 @@ export const CLA = 0x80
|
|
|
9
9
|
export enum INS {
|
|
10
10
|
GET_VERSION = 0x00,
|
|
11
11
|
GET_PUBLIC_KEY = 0x01,
|
|
12
|
-
SIGN_HASH = 0x02
|
|
12
|
+
SIGN_HASH = 0x02,
|
|
13
|
+
SIGN_TX = 0x03
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export const GROUP_NUM = 4
|
|
@@ -32,8 +33,7 @@ export default class AlephiumApp {
|
|
|
32
33
|
return `${response[0]}.${response[1]}.${response[2]}`
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType): Promise<readonly [Account, number]> {
|
|
36
|
+
async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [Account, number]> {
|
|
37
37
|
if ((targetGroup ?? 0) >= GROUP_NUM) {
|
|
38
38
|
throw Error(`Invalid targetGroup: ${targetGroup}`)
|
|
39
39
|
}
|
|
@@ -44,7 +44,8 @@ export default class AlephiumApp {
|
|
|
44
44
|
|
|
45
45
|
const p1 = targetGroup === undefined ? 0x00 : GROUP_NUM
|
|
46
46
|
const p2 = targetGroup === undefined ? 0x00 : targetGroup
|
|
47
|
-
const
|
|
47
|
+
const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
|
|
48
|
+
const response = await this.transport.send(CLA, INS.GET_PUBLIC_KEY, p1, p2, payload)
|
|
48
49
|
const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex')
|
|
49
50
|
const address = addressFromPublicKey(publicKey)
|
|
50
51
|
const group = groupOfAddress(address)
|
|
@@ -63,12 +64,40 @@ export default class AlephiumApp {
|
|
|
63
64
|
const response = await this.transport.send(CLA, INS.SIGN_HASH, 0x00, 0x00, data, [StatusCodes.OK])
|
|
64
65
|
console.log(`response ${response.length} - ${response.toString('hex')}`)
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
const rLen = response.slice(3, 4)[0]
|
|
68
|
-
const r = response.slice(4, 4 + rLen)
|
|
69
|
-
const sLen = response.slice(5 + rLen, 6 + rLen)[0]
|
|
70
|
-
const s = response.slice(6 + rLen, 6 + rLen + sLen)
|
|
71
|
-
console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
|
|
72
|
-
return encodeHexSignature(r.toString('hex'), s.toString('hex'))
|
|
67
|
+
return decodeSignature(response)
|
|
73
68
|
}
|
|
69
|
+
|
|
70
|
+
async signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string> {
|
|
71
|
+
console.log(`unsigned tx size: ${unsignedTx.length}`)
|
|
72
|
+
const encodedPath = serde.serializePath(path)
|
|
73
|
+
const firstFrameTxLength = 256 - 25;
|
|
74
|
+
const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length)
|
|
75
|
+
const data = Buffer.concat([encodedPath, txData])
|
|
76
|
+
let response = await this.transport.send(CLA, INS.SIGN_TX, 0x00, 0x00, data, [StatusCodes.OK])
|
|
77
|
+
if (unsignedTx.length <= firstFrameTxLength) {
|
|
78
|
+
return decodeSignature(response)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const frameLength = 256 - 5
|
|
82
|
+
let fromIndex = firstFrameTxLength
|
|
83
|
+
while (fromIndex < unsignedTx.length) {
|
|
84
|
+
const remain = unsignedTx.length - fromIndex
|
|
85
|
+
const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length
|
|
86
|
+
const data = unsignedTx.slice(fromIndex, toIndex)
|
|
87
|
+
response = await this.transport.send(CLA, INS.SIGN_TX, 0x01, 0x00, data, [StatusCodes.OK])
|
|
88
|
+
fromIndex = toIndex
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return decodeSignature(response)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function decodeSignature(response: Buffer): string {
|
|
96
|
+
// Decode signature: https://bitcoin.stackexchange.com/a/12556
|
|
97
|
+
const rLen = response.slice(3, 4)[0]
|
|
98
|
+
const r = response.slice(4, 4 + rLen)
|
|
99
|
+
const sLen = response.slice(5 + rLen, 6 + rLen)[0]
|
|
100
|
+
const s = response.slice(6 + rLen, 6 + rLen + sLen)
|
|
101
|
+
console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
|
|
102
|
+
return encodeHexSignature(r.toString('hex'), s.toString('hex'))
|
|
74
103
|
}
|