@ledgerhq/hw-app-eth 6.21.3 → 6.22.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/README.md +51 -100
- package/erc20.js +1 -1
- package/lib/Eth.d.ts +14 -33
- package/lib/Eth.d.ts.map +1 -1
- package/lib/Eth.js +273 -305
- package/lib/Eth.js.map +1 -1
- package/lib/services/ledger/contracts.d.ts +14 -0
- package/lib/services/ledger/contracts.d.ts.map +1 -0
- package/lib/{contracts.js → services/ledger/contracts.js} +0 -0
- package/lib/services/ledger/contracts.js.map +1 -0
- package/lib/services/ledger/erc20.d.ts +22 -0
- package/lib/services/ledger/erc20.d.ts.map +1 -0
- package/lib/{erc20.js → services/ledger/erc20.js} +0 -0
- package/lib/services/ledger/erc20.js.map +1 -0
- package/lib/services/ledger/index.d.ts +4 -0
- package/lib/services/ledger/index.d.ts.map +1 -0
- package/lib/services/ledger/index.js +168 -0
- package/lib/services/ledger/index.js.map +1 -0
- package/lib/services/ledger/loadConfig.d.ts +3 -0
- package/lib/services/ledger/loadConfig.d.ts.map +1 -0
- package/lib/{loadConfig.js → services/ledger/loadConfig.js} +0 -0
- package/lib/services/ledger/loadConfig.js.map +1 -0
- package/lib/services/ledger/nfts.d.ts +10 -0
- package/lib/services/ledger/nfts.d.ts.map +1 -0
- package/lib/{nfts.js → services/ledger/nfts.js} +3 -14
- package/lib/services/ledger/nfts.js.map +1 -0
- package/lib/services/types.d.ts +18 -0
- package/lib/services/types.d.ts.map +1 -0
- package/lib/services/types.js +3 -0
- package/lib/services/types.js.map +1 -0
- package/lib/utils.d.ts +8 -27
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +68 -68
- package/lib/utils.js.map +1 -1
- package/lib-es/Eth.d.ts +14 -33
- package/lib-es/Eth.d.ts.map +1 -1
- package/lib-es/Eth.js +261 -296
- package/lib-es/Eth.js.map +1 -1
- package/{lib → lib-es/services/ledger}/contracts.d.ts +1 -1
- package/lib-es/services/ledger/contracts.d.ts.map +1 -0
- package/lib-es/services/ledger/contracts.js +96 -0
- package/lib-es/services/ledger/contracts.js.map +1 -0
- package/{lib → lib-es/services/ledger}/erc20.d.ts +0 -0
- package/lib-es/services/ledger/erc20.d.ts.map +1 -0
- package/lib-es/services/ledger/erc20.js +64 -0
- package/lib-es/services/ledger/erc20.js.map +1 -0
- package/lib-es/services/ledger/index.d.ts +4 -0
- package/lib-es/services/ledger/index.d.ts.map +1 -0
- package/lib-es/services/ledger/index.js +166 -0
- package/lib-es/services/ledger/index.js.map +1 -0
- package/lib-es/services/ledger/loadConfig.d.ts +3 -0
- package/lib-es/services/ledger/loadConfig.d.ts.map +1 -0
- package/lib-es/services/ledger/loadConfig.js +20 -0
- package/lib-es/services/ledger/loadConfig.js.map +1 -0
- package/{lib → lib-es/services/ledger}/nfts.d.ts +2 -3
- package/lib-es/services/ledger/nfts.d.ts.map +1 -0
- package/lib-es/services/ledger/nfts.js +83 -0
- package/lib-es/services/ledger/nfts.js.map +1 -0
- package/lib-es/services/types.d.ts +18 -0
- package/lib-es/services/types.d.ts.map +1 -0
- package/lib-es/services/types.js +2 -0
- package/lib-es/services/types.js.map +1 -0
- package/lib-es/utils.d.ts +8 -27
- package/lib-es/utils.d.ts.map +1 -1
- package/lib-es/utils.js +66 -61
- package/lib-es/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/Eth.ts +124 -258
- package/src/{contracts.ts → services/ledger/contracts.ts} +1 -1
- package/src/{erc20.ts → services/ledger/erc20.ts} +0 -0
- package/src/services/ledger/index.ts +119 -0
- package/src/services/ledger/loadConfig.ts +14 -0
- package/src/{nfts.ts → services/ledger/nfts.ts} +5 -18
- package/src/services/types.ts +28 -0
- package/src/utils.ts +72 -94
- package/tests/Eth.test.ts +2 -2
- package/lib/contracts.d.ts.map +0 -1
- package/lib/contracts.js.map +0 -1
- package/lib/erc20.d.ts.map +0 -1
- package/lib/erc20.js.map +0 -1
- package/lib/loadConfig.d.ts +0 -7
- package/lib/loadConfig.d.ts.map +0 -1
- package/lib/loadConfig.js.map +0 -1
- package/lib/nfts.d.ts.map +0 -1
- package/lib/nfts.js.map +0 -1
- package/src/loadConfig.ts +0 -23
package/src/Eth.ts
CHANGED
|
@@ -15,16 +15,13 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
********************************************************************************/
|
|
17
17
|
// FIXME drop:
|
|
18
|
-
import { splitPath, foreach } from "./utils";
|
|
19
|
-
import { log } from "@ledgerhq/logs";
|
|
20
18
|
import { EthAppPleaseEnableContractData } from "@ledgerhq/errors";
|
|
21
19
|
import type Transport from "@ledgerhq/hw-transport";
|
|
22
20
|
import { BigNumber } from "bignumber.js";
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import { getNFTInfo, loadNftPlugin } from "./nfts";
|
|
21
|
+
import { decodeTxInfo } from "./utils";
|
|
22
|
+
// NB: these are temporary import for the deprecated fallback mechanism
|
|
23
|
+
import { LedgerEthTransactionResolution, LoadConfig } from "./services/types";
|
|
24
|
+
import ledgerService from "./services/ledger";
|
|
28
25
|
|
|
29
26
|
export type StarkQuantizationType =
|
|
30
27
|
| "eth"
|
|
@@ -40,6 +37,22 @@ const starkQuantizationTypeMap = {
|
|
|
40
37
|
erc721mintable: 5,
|
|
41
38
|
};
|
|
42
39
|
|
|
40
|
+
function splitPath(path: string): number[] {
|
|
41
|
+
const result: number[] = [];
|
|
42
|
+
const components = path.split("/");
|
|
43
|
+
components.forEach((element) => {
|
|
44
|
+
let number = parseInt(element, 10);
|
|
45
|
+
if (isNaN(number)) {
|
|
46
|
+
return; // FIXME shouldn't it throws instead?
|
|
47
|
+
}
|
|
48
|
+
if (element.length > 1 && element[element.length - 1] === "'") {
|
|
49
|
+
number += 0x80000000;
|
|
50
|
+
}
|
|
51
|
+
result.push(number);
|
|
52
|
+
});
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
function hexBuffer(str: string): Buffer {
|
|
44
57
|
return Buffer.from(str.startsWith("0x") ? str.slice(2) : str, "hex");
|
|
45
58
|
}
|
|
@@ -168,119 +181,72 @@ export default class Eth {
|
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* @param {*} info: a blob from "erc20.js" utilities that contains all token information.
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* import { byContractAddressAndChainId } from "@ledgerhq/hw-app-eth/erc20"
|
|
181
|
-
* const zrxInfo = byContractAddressAndChainId("0xe41d2489571d322189246dafa5ebde1f4699f498", chainId)
|
|
182
|
-
* if (zrxInfo) await appEth.provideERC20TokenInformation(zrxInfo)
|
|
183
|
-
* const signed = await appEth.signTransaction(path, rawTxHex)
|
|
184
|
-
*/
|
|
185
|
-
provideERC20TokenInformation({ data }: { data: Buffer }): Promise<boolean> {
|
|
186
|
-
return provideERC20TokenInformation(this.transport, data);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign
|
|
184
|
+
* You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign.
|
|
185
|
+
*
|
|
186
|
+
* @param path: the BIP32 path to sign the transaction on
|
|
187
|
+
* @param rawTxHex: the raw ethereum transaction in hexadecimal to sign
|
|
188
|
+
* @param resolution: resolution is an object with all "resolved" metadata necessary to allow the device to clear sign information. This includes: ERC20 token information, plugins, contracts, NFT signatures,... You must explicitly provide something to avoid having a warning. By default, you can use Ledger's service or your own resolution service. See services/types.js for the contract. Setting the value to "null" will fallback everything to blind signing but will still allow the device to sign the transaction.
|
|
191
189
|
* @example
|
|
192
|
-
|
|
190
|
+
import ledgerService from "@ledgerhq/hw-app-eth/lib/services/ledger"
|
|
191
|
+
const tx = "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080"; // raw tx to sign
|
|
192
|
+
const resolution = await ledgerService.resolveTransaction(tx);
|
|
193
|
+
const result = eth.signTransaction("44'/60'/0'/0/0", tx, resolution);
|
|
194
|
+
console.log(result);
|
|
193
195
|
*/
|
|
194
196
|
async signTransaction(
|
|
195
197
|
path: string,
|
|
196
|
-
rawTxHex: string
|
|
198
|
+
rawTxHex: string,
|
|
199
|
+
resolution?: LedgerEthTransactionResolution | null
|
|
197
200
|
): Promise<{
|
|
198
201
|
s: string;
|
|
199
202
|
v: string;
|
|
200
203
|
r: string;
|
|
201
204
|
}> {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let response;
|
|
212
|
-
// Check if the TX is encoded following EIP 155
|
|
213
|
-
const rlpTx = ethers.utils.RLP.decode(rlpData).map((hex) =>
|
|
214
|
-
Buffer.from(hex.slice(2), "hex")
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
let vrsOffset = 0;
|
|
218
|
-
let chainId = new BigNumber(0);
|
|
219
|
-
let chainIdTruncated = 0;
|
|
220
|
-
|
|
221
|
-
const rlpDecoded = ethers.utils.RLP.decode(rlpData);
|
|
222
|
-
|
|
223
|
-
let decodedTx;
|
|
224
|
-
if (txType === 2) {
|
|
225
|
-
// EIP1559
|
|
226
|
-
decodedTx = {
|
|
227
|
-
data: rlpDecoded[7],
|
|
228
|
-
to: rlpDecoded[5],
|
|
229
|
-
chainId: rlpTx[0],
|
|
230
|
-
};
|
|
231
|
-
} else if (txType === 1) {
|
|
232
|
-
// EIP2930
|
|
233
|
-
decodedTx = {
|
|
234
|
-
data: rlpDecoded[6],
|
|
235
|
-
to: rlpDecoded[4],
|
|
236
|
-
chainId: rlpTx[0],
|
|
237
|
-
};
|
|
238
|
-
} else {
|
|
239
|
-
// Legacy tx
|
|
240
|
-
decodedTx = {
|
|
241
|
-
data: rlpDecoded[5],
|
|
242
|
-
to: rlpDecoded[3],
|
|
243
|
-
// Default to 1 for non EIP 155 txs
|
|
244
|
-
chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("0x01", "hex"),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (txType === null && rlpTx.length > 6) {
|
|
249
|
-
const rlpVrs = Buffer.from(
|
|
250
|
-
ethers.utils.RLP.encode(rlpTx.slice(-3)).slice(2),
|
|
251
|
-
"hex"
|
|
205
|
+
if (resolution === undefined) {
|
|
206
|
+
console.warn(
|
|
207
|
+
"hw-app-eth: signTransaction(path, rawTxHex, resolution): " +
|
|
208
|
+
"please provide the 'resolution' parameter. " +
|
|
209
|
+
"See https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-eth/README.md " +
|
|
210
|
+
"– the previous signature is deprecated and providing the 3rd 'resolution' parameter explicitly will become mandatory so you have the control on the resolution and the fallback mecanism (e.g. fallback to blind signing or not)." +
|
|
211
|
+
"// Possible solution:\n" +
|
|
212
|
+
" + import ledgerService from '@ledgerhq/hw-app-eth/lib/services/ledger';\n" +
|
|
213
|
+
" + const resolution = await ledgerService.resolveTransaction(rawTxHex);"
|
|
252
214
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// First byte > 0xf7 means the length of the list length doesn't fit in a single byte.
|
|
257
|
-
if (rlpVrs[0] > 0xf7) {
|
|
258
|
-
// Increment vrsOffset to account for that extra byte.
|
|
259
|
-
vrsOffset++;
|
|
260
|
-
|
|
261
|
-
// Compute size of the list length.
|
|
262
|
-
const sizeOfListLen = rlpVrs[0] - 0xf7;
|
|
263
|
-
|
|
264
|
-
// Increase rlpOffset by the size of the list length.
|
|
265
|
-
vrsOffset += sizeOfListLen - 1;
|
|
266
|
-
}
|
|
215
|
+
resolution = await ledgerService
|
|
216
|
+
.resolveTransaction(rawTxHex, this.loadConfig)
|
|
217
|
+
.catch(() => null);
|
|
267
218
|
}
|
|
268
219
|
|
|
269
|
-
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
220
|
+
// provide to the device resolved information to make it clear sign the signature
|
|
221
|
+
if (resolution) {
|
|
222
|
+
for (const plugin of resolution.plugin) {
|
|
223
|
+
await setPlugin(this.transport, plugin);
|
|
224
|
+
}
|
|
225
|
+
for (const { payload, signature } of resolution.externalPlugin) {
|
|
226
|
+
await setExternalPlugin(this.transport, payload, signature);
|
|
227
|
+
}
|
|
228
|
+
for (const nft of resolution.nfts) {
|
|
229
|
+
await provideNFTInformation(this.transport, Buffer.from(nft, "hex"));
|
|
230
|
+
}
|
|
231
|
+
for (const data of resolution.erc20Tokens) {
|
|
232
|
+
await provideERC20TokenInformation(
|
|
233
|
+
this.transport,
|
|
234
|
+
Buffer.from(data, "hex")
|
|
235
|
+
);
|
|
278
236
|
}
|
|
279
|
-
chainIdTruncated = chainIdTruncatedBuf.readUInt32BE(0);
|
|
280
237
|
}
|
|
281
238
|
|
|
239
|
+
const rawTx = Buffer.from(rawTxHex, "hex");
|
|
240
|
+
const { vrsOffset, txType, chainId, chainIdTruncated } = decodeTxInfo(
|
|
241
|
+
rawTx
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const paths = splitPath(path);
|
|
245
|
+
let response;
|
|
246
|
+
let offset = 0;
|
|
282
247
|
while (offset !== rawTx.length) {
|
|
283
|
-
const
|
|
248
|
+
const first = offset === 0;
|
|
249
|
+
const maxChunkSize = first ? 150 - 1 - paths.length * 4 : 150;
|
|
284
250
|
let chunkSize =
|
|
285
251
|
offset + maxChunkSize > rawTx.length
|
|
286
252
|
? rawTx.length - offset
|
|
@@ -292,10 +258,10 @@ export default class Eth {
|
|
|
292
258
|
}
|
|
293
259
|
|
|
294
260
|
const buffer = Buffer.alloc(
|
|
295
|
-
|
|
261
|
+
first ? 1 + paths.length * 4 + chunkSize : chunkSize
|
|
296
262
|
);
|
|
297
263
|
|
|
298
|
-
if (
|
|
264
|
+
if (first) {
|
|
299
265
|
buffer[0] = paths.length;
|
|
300
266
|
paths.forEach((element, index) => {
|
|
301
267
|
buffer.writeUInt32BE(element, 1 + 4 * index);
|
|
@@ -305,137 +271,42 @@ export default class Eth {
|
|
|
305
271
|
rawTx.copy(buffer, 0, offset, offset + chunkSize);
|
|
306
272
|
}
|
|
307
273
|
|
|
308
|
-
|
|
274
|
+
response = await this.transport
|
|
275
|
+
.send(0xe0, 0x04, first ? 0x00 : 0x80, 0x00, buffer)
|
|
276
|
+
.catch((e) => {
|
|
277
|
+
throw remapTransactionRelatedErrors(e);
|
|
278
|
+
});
|
|
279
|
+
|
|
309
280
|
offset += chunkSize;
|
|
310
281
|
}
|
|
311
282
|
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
address,
|
|
315
|
-
chainIdTruncated,
|
|
316
|
-
this.loadConfig
|
|
317
|
-
);
|
|
318
|
-
if (nftInfo) {
|
|
319
|
-
log(
|
|
320
|
-
"ethereum",
|
|
321
|
-
"loading nft info for " +
|
|
322
|
-
nftInfo.contractAddress +
|
|
323
|
-
" (" +
|
|
324
|
-
nftInfo.collectionName +
|
|
325
|
-
")"
|
|
326
|
-
);
|
|
327
|
-
await provideNFTInformation(this.transport, nftInfo.data);
|
|
328
|
-
} else {
|
|
329
|
-
const erc20Info = byContractAddressAndChainId(
|
|
330
|
-
address,
|
|
331
|
-
chainIdTruncated
|
|
332
|
-
);
|
|
333
|
-
if (erc20Info) {
|
|
334
|
-
log(
|
|
335
|
-
"ethereum",
|
|
336
|
-
"loading erc20token info for " +
|
|
337
|
-
erc20Info.contractAddress +
|
|
338
|
-
" (" +
|
|
339
|
-
erc20Info.ticker +
|
|
340
|
-
")"
|
|
341
|
-
);
|
|
342
|
-
await provideERC20TokenInformation(this.transport, erc20Info.data);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
if (decodedTx.data.length >= 10) {
|
|
348
|
-
const selector = decodedTx.data.substring(0, 10);
|
|
349
|
-
const nftPluginPayload = await loadNftPlugin(
|
|
350
|
-
decodedTx.to,
|
|
351
|
-
selector,
|
|
352
|
-
chainIdTruncated,
|
|
353
|
-
this.loadConfig
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
if (nftPluginPayload) {
|
|
357
|
-
await setPlugin(this.transport, nftPluginPayload);
|
|
358
|
-
} else {
|
|
359
|
-
const infos = await loadInfosForContractMethod(
|
|
360
|
-
decodedTx.to,
|
|
361
|
-
selector,
|
|
362
|
-
chainIdTruncated,
|
|
363
|
-
this.loadConfig
|
|
364
|
-
);
|
|
283
|
+
const response_byte: number = response[0];
|
|
284
|
+
let v = "";
|
|
365
285
|
|
|
366
|
-
|
|
367
|
-
|
|
286
|
+
if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) {
|
|
287
|
+
const oneByteChainId = (chainIdTruncated * 2 + 35) % 256;
|
|
368
288
|
|
|
369
|
-
|
|
370
|
-
log("ethereum", "loading plugin for " + selector);
|
|
371
|
-
await setExternalPlugin(this.transport, payload, signature);
|
|
372
|
-
}
|
|
289
|
+
const ecc_parity = Math.abs(response_byte - oneByteChainId);
|
|
373
290
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (seg === "-1" && Array.isArray(value)) {
|
|
381
|
-
return value[value.length - 1];
|
|
382
|
-
}
|
|
383
|
-
return value[seg];
|
|
384
|
-
}, args);
|
|
385
|
-
await provideForContract(address);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
} else {
|
|
389
|
-
log("ethereum", "no infos for selector " + selector);
|
|
390
|
-
}
|
|
291
|
+
if (txType != null) {
|
|
292
|
+
// For EIP2930 and EIP1559 tx, v is simply the parity.
|
|
293
|
+
v = ecc_parity % 2 == 1 ? "00" : "01";
|
|
294
|
+
} else {
|
|
295
|
+
// Legacy type transaction with a big chain ID
|
|
296
|
+
v = chainId.times(2).plus(35).plus(ecc_parity).toString(16);
|
|
391
297
|
}
|
|
392
|
-
|
|
298
|
+
} else {
|
|
299
|
+
v = response_byte.toString(16);
|
|
393
300
|
}
|
|
394
301
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
response = apduResponse;
|
|
400
|
-
})
|
|
401
|
-
).then(
|
|
402
|
-
() => {
|
|
403
|
-
const response_byte: number = response.slice(0, 1)[0];
|
|
404
|
-
let v = "";
|
|
405
|
-
|
|
406
|
-
if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) {
|
|
407
|
-
const oneByteChainId = (chainIdTruncated * 2 + 35) % 256;
|
|
408
|
-
|
|
409
|
-
const ecc_parity = Math.abs(response_byte - oneByteChainId);
|
|
410
|
-
|
|
411
|
-
if (txType != null) {
|
|
412
|
-
// For EIP2930 and EIP1559 tx, v is simply the parity.
|
|
413
|
-
v = ecc_parity % 2 == 1 ? "00" : "01";
|
|
414
|
-
} else {
|
|
415
|
-
// Legacy type transaction with a big chain ID
|
|
416
|
-
v = chainId.times(2).plus(35).plus(ecc_parity).toString(16);
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
v = response_byte.toString(16);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01").
|
|
423
|
-
if (v.length % 2 == 1) {
|
|
424
|
-
v = "0" + v;
|
|
425
|
-
}
|
|
302
|
+
// Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01").
|
|
303
|
+
if (v.length % 2 == 1) {
|
|
304
|
+
v = "0" + v;
|
|
305
|
+
}
|
|
426
306
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
v,
|
|
431
|
-
r,
|
|
432
|
-
s,
|
|
433
|
-
};
|
|
434
|
-
},
|
|
435
|
-
(e) => {
|
|
436
|
-
throw remapTransactionRelatedErrors(e);
|
|
437
|
-
}
|
|
438
|
-
);
|
|
307
|
+
const r = response.slice(1, 1 + 32).toString("hex");
|
|
308
|
+
const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
|
|
309
|
+
return { v, r, s };
|
|
439
310
|
}
|
|
440
311
|
|
|
441
312
|
/**
|
|
@@ -470,7 +341,7 @@ export default class Eth {
|
|
|
470
341
|
console.log("Signature 0x" + result['r'] + result['s'] + v);
|
|
471
342
|
})
|
|
472
343
|
*/
|
|
473
|
-
signPersonalMessage(
|
|
344
|
+
async signPersonalMessage(
|
|
474
345
|
path: string,
|
|
475
346
|
messageHex: string
|
|
476
347
|
): Promise<{
|
|
@@ -481,7 +352,6 @@ export default class Eth {
|
|
|
481
352
|
const paths = splitPath(path);
|
|
482
353
|
let offset = 0;
|
|
483
354
|
const message = Buffer.from(messageHex, "hex");
|
|
484
|
-
const toSend: Buffer[] = [];
|
|
485
355
|
let response;
|
|
486
356
|
|
|
487
357
|
while (offset !== message.length) {
|
|
@@ -510,26 +380,21 @@ export default class Eth {
|
|
|
510
380
|
message.copy(buffer, 0, offset, offset + chunkSize);
|
|
511
381
|
}
|
|
512
382
|
|
|
513
|
-
|
|
383
|
+
response = await this.transport.send(
|
|
384
|
+
0xe0,
|
|
385
|
+
0x08,
|
|
386
|
+
offset === 0 ? 0x00 : 0x80,
|
|
387
|
+
0x00,
|
|
388
|
+
buffer
|
|
389
|
+
);
|
|
390
|
+
|
|
514
391
|
offset += chunkSize;
|
|
515
392
|
}
|
|
516
393
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
response = apduResponse;
|
|
522
|
-
})
|
|
523
|
-
).then(() => {
|
|
524
|
-
const v = response[0];
|
|
525
|
-
const r = response.slice(1, 1 + 32).toString("hex");
|
|
526
|
-
const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
|
|
527
|
-
return {
|
|
528
|
-
v,
|
|
529
|
-
r,
|
|
530
|
-
s,
|
|
531
|
-
};
|
|
532
|
-
});
|
|
394
|
+
const v = response[0];
|
|
395
|
+
const r = response.slice(1, 1 + 32).toString("hex");
|
|
396
|
+
const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
|
|
397
|
+
return { v, r, s };
|
|
533
398
|
}
|
|
534
399
|
|
|
535
400
|
/**
|
|
@@ -1256,27 +1121,28 @@ export default class Eth {
|
|
|
1256
1121
|
);
|
|
1257
1122
|
}
|
|
1258
1123
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1124
|
+
provideERC20TokenInformation({ data }: { data: Buffer }): Promise<boolean> {
|
|
1125
|
+
console.warn(
|
|
1126
|
+
"hw-app-eth: eth.provideERC20TokenInformation is deprecated. signTransaction solves this for you when providing it in `resolution`."
|
|
1127
|
+
);
|
|
1128
|
+
return provideERC20TokenInformation(this.transport, data);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1265
1131
|
setExternalPlugin(
|
|
1266
1132
|
pluginName: string,
|
|
1267
1133
|
contractAddress: string,
|
|
1268
1134
|
selector: string
|
|
1269
1135
|
): Promise<boolean> {
|
|
1136
|
+
console.warn(
|
|
1137
|
+
"hw-app-eth: eth.setExternalPlugin is deprecated. signTransaction solves this for you when providing it in `resolution`."
|
|
1138
|
+
);
|
|
1270
1139
|
return setExternalPlugin(this.transport, pluginName, selector);
|
|
1271
1140
|
}
|
|
1272
1141
|
|
|
1273
|
-
/**
|
|
1274
|
-
* Set the plugin (internal or external) that should be used to parse the next transaction
|
|
1275
|
-
*
|
|
1276
|
-
* @param data string containing the payload and signature that will be parsed and verified by the device.
|
|
1277
|
-
* @return True if the method was executed successfully
|
|
1278
|
-
*/
|
|
1279
1142
|
setPlugin(data: string): Promise<boolean> {
|
|
1143
|
+
console.warn(
|
|
1144
|
+
"hw-app-eth: eth.setPlugin is deprecated. signTransaction solves this for you when providing it in `resolution`."
|
|
1145
|
+
);
|
|
1280
1146
|
return setPlugin(this.transport, data);
|
|
1281
1147
|
}
|
|
1282
1148
|
}
|
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// This implements the resolution of a Transaction using Ledger's own API
|
|
2
|
+
import { log } from "@ledgerhq/logs";
|
|
3
|
+
import { ethers } from "ethers";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
LedgerEthTransactionResolution,
|
|
7
|
+
LedgerEthTransactionService,
|
|
8
|
+
} from "../types";
|
|
9
|
+
import { loadInfosForContractMethod } from "./contracts";
|
|
10
|
+
import { byContractAddressAndChainId } from "./erc20";
|
|
11
|
+
import { getNFTInfo, loadNftPlugin } from "./nfts";
|
|
12
|
+
import { decodeTxInfo } from "../../utils";
|
|
13
|
+
|
|
14
|
+
const ledgerService: LedgerEthTransactionService = {
|
|
15
|
+
resolveTransaction: async (rawTxHex, loadConfig) => {
|
|
16
|
+
const resolution: LedgerEthTransactionResolution = {
|
|
17
|
+
erc20Tokens: [],
|
|
18
|
+
nfts: [],
|
|
19
|
+
externalPlugin: [],
|
|
20
|
+
plugin: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function provideERC20TokenInformation(dataHex: string) {
|
|
24
|
+
resolution.erc20Tokens.push(dataHex);
|
|
25
|
+
}
|
|
26
|
+
function provideNFTInformation(dataHex: string) {
|
|
27
|
+
resolution.nfts.push(dataHex);
|
|
28
|
+
}
|
|
29
|
+
function setExternalPlugin(payload: string, signature: string) {
|
|
30
|
+
resolution.externalPlugin.push({ payload, signature });
|
|
31
|
+
}
|
|
32
|
+
function setPlugin(dataHex: string) {
|
|
33
|
+
resolution.plugin.push(dataHex);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const rawTx = Buffer.from(rawTxHex, "hex");
|
|
37
|
+
const { decodedTx, chainIdTruncated } = decodeTxInfo(rawTx);
|
|
38
|
+
const provideForContract = async (address) => {
|
|
39
|
+
const nftInfo = await getNFTInfo(address, chainIdTruncated, loadConfig);
|
|
40
|
+
if (nftInfo) {
|
|
41
|
+
log(
|
|
42
|
+
"ethereum",
|
|
43
|
+
"loaded nft info for " +
|
|
44
|
+
nftInfo.contractAddress +
|
|
45
|
+
" (" +
|
|
46
|
+
nftInfo.collectionName +
|
|
47
|
+
")"
|
|
48
|
+
);
|
|
49
|
+
provideNFTInformation(nftInfo.data);
|
|
50
|
+
} else {
|
|
51
|
+
const erc20Info = byContractAddressAndChainId(
|
|
52
|
+
address,
|
|
53
|
+
chainIdTruncated
|
|
54
|
+
);
|
|
55
|
+
if (erc20Info) {
|
|
56
|
+
log(
|
|
57
|
+
"ethereum",
|
|
58
|
+
"loaded erc20token info for " +
|
|
59
|
+
erc20Info.contractAddress +
|
|
60
|
+
" (" +
|
|
61
|
+
erc20Info.ticker +
|
|
62
|
+
")"
|
|
63
|
+
);
|
|
64
|
+
provideERC20TokenInformation(erc20Info.data.toString("hex"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (decodedTx.data.length >= 10) {
|
|
70
|
+
const selector = decodedTx.data.substring(0, 10);
|
|
71
|
+
const nftPluginPayload = await loadNftPlugin(
|
|
72
|
+
decodedTx.to,
|
|
73
|
+
selector,
|
|
74
|
+
chainIdTruncated,
|
|
75
|
+
loadConfig
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (nftPluginPayload) {
|
|
79
|
+
setPlugin(nftPluginPayload);
|
|
80
|
+
} else {
|
|
81
|
+
const infos = await loadInfosForContractMethod(
|
|
82
|
+
decodedTx.to,
|
|
83
|
+
selector,
|
|
84
|
+
chainIdTruncated,
|
|
85
|
+
loadConfig
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (infos) {
|
|
89
|
+
const { plugin, payload, signature, erc20OfInterest, abi } = infos;
|
|
90
|
+
|
|
91
|
+
if (plugin) {
|
|
92
|
+
log("ethereum", "found plugin for " + selector);
|
|
93
|
+
setExternalPlugin(payload, signature);
|
|
94
|
+
}
|
|
95
|
+
if (erc20OfInterest && erc20OfInterest.length && abi) {
|
|
96
|
+
const contract = new ethers.utils.Interface(abi);
|
|
97
|
+
const args = contract.parseTransaction(decodedTx).args;
|
|
98
|
+
for (const path of erc20OfInterest) {
|
|
99
|
+
const address = path.split(".").reduce((value, seg) => {
|
|
100
|
+
if (seg === "-1" && Array.isArray(value)) {
|
|
101
|
+
return value[value.length - 1];
|
|
102
|
+
}
|
|
103
|
+
return value[seg];
|
|
104
|
+
}, args);
|
|
105
|
+
provideForContract(address);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
log("ethereum", "no infos for selector " + selector);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
provideForContract(decodedTx.to);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return resolution;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default ledgerService;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LoadConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
const defaultLoadConfig = {
|
|
4
|
+
nftExplorerBaseURL: null, // set a value when an official production endpoint is released
|
|
5
|
+
pluginBaseURL: "https://cdn.live.ledger.com",
|
|
6
|
+
extraPlugins: null,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function getLoadConfig(userLoadConfig?: LoadConfig): LoadConfig {
|
|
10
|
+
return {
|
|
11
|
+
...defaultLoadConfig,
|
|
12
|
+
...userLoadConfig,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { getLoadConfig } from "./loadConfig";
|
|
3
|
-
import type { LoadConfig } from "
|
|
4
|
-
import { log } from "@ledgerhq/logs";
|
|
3
|
+
import type { LoadConfig } from "../types";
|
|
5
4
|
|
|
6
5
|
type NftInfo = {
|
|
7
6
|
contractAddress: string;
|
|
8
7
|
collectionName: string;
|
|
9
|
-
data:
|
|
8
|
+
data: string;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
type BackendResponse = {
|
|
@@ -21,13 +20,7 @@ export const getNFTInfo = async (
|
|
|
21
20
|
const { nftExplorerBaseURL } = getLoadConfig(userLoadConfig);
|
|
22
21
|
if (!nftExplorerBaseURL) return;
|
|
23
22
|
const url = `${nftExplorerBaseURL}/${chainId}/contracts/${contractAddress}`;
|
|
24
|
-
const response = await axios
|
|
25
|
-
.get<BackendResponse>(url)
|
|
26
|
-
.then((r) => r.data)
|
|
27
|
-
.catch((e) => {
|
|
28
|
-
log("error", "could not fetch from " + url + ": " + String(e));
|
|
29
|
-
return null;
|
|
30
|
-
});
|
|
23
|
+
const response = await axios.get<BackendResponse>(url).then((r) => r.data);
|
|
31
24
|
if (!response) return;
|
|
32
25
|
|
|
33
26
|
const payload = response["payload"];
|
|
@@ -36,7 +29,7 @@ export const getNFTInfo = async (
|
|
|
36
29
|
return {
|
|
37
30
|
contractAddress: contractAddress,
|
|
38
31
|
collectionName: collectionName,
|
|
39
|
-
data:
|
|
32
|
+
data: payload,
|
|
40
33
|
};
|
|
41
34
|
};
|
|
42
35
|
|
|
@@ -50,13 +43,7 @@ export const loadNftPlugin = async (
|
|
|
50
43
|
if (!nftExplorerBaseURL) return;
|
|
51
44
|
const url = `${nftExplorerBaseURL}/${chainId}/contracts/${contractAddress}/plugin-selector/${selector}`;
|
|
52
45
|
|
|
53
|
-
const response = await axios
|
|
54
|
-
.get<BackendResponse>(url)
|
|
55
|
-
.then((r) => r.data)
|
|
56
|
-
.catch((e) => {
|
|
57
|
-
log("error", "could not fetch from " + url + ": " + String(e));
|
|
58
|
-
return null;
|
|
59
|
-
});
|
|
46
|
+
const response = await axios.get<BackendResponse>(url).then((r) => r.data);
|
|
60
47
|
if (!response) return;
|
|
61
48
|
|
|
62
49
|
const payload = response["payload"];
|