@bytezhang/ledger-adapter 0.0.17 → 0.0.20
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/index.d.mts +126 -6
- package/dist/index.d.ts +126 -6
- package/dist/index.js +502 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +501 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
SignerEth: () => SignerEth,
|
|
39
39
|
SignerManager: () => SignerManager,
|
|
40
40
|
SignerSol: () => SignerSol,
|
|
41
|
+
SignerTron: () => SignerTron,
|
|
41
42
|
clearRegistry: () => clearRegistry,
|
|
42
43
|
deviceActionToPromise: () => deviceActionToPromise,
|
|
43
44
|
getTransportProvider: () => getTransportProvider,
|
|
@@ -63,7 +64,7 @@ function isDeviceLockedError(err) {
|
|
|
63
64
|
if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
|
|
64
65
|
if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
|
|
65
66
|
if (e._tag === "DeviceLockedError") return true;
|
|
66
|
-
if (typeof e.message === "string" && /locked/i.test(e.message)) return true;
|
|
67
|
+
if (typeof e.message === "string" && /locked|device exchange error/i.test(e.message)) return true;
|
|
67
68
|
if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
|
|
68
69
|
if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
|
|
69
70
|
return false;
|
|
@@ -99,7 +100,7 @@ function isDeviceDisconnectedError(err) {
|
|
|
99
100
|
if (!err || typeof err !== "object") return false;
|
|
100
101
|
const e = err;
|
|
101
102
|
if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
|
|
102
|
-
if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found/i.test(e.message)) return true;
|
|
103
|
+
if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found|timed out.*locked/i.test(e.message)) return true;
|
|
103
104
|
return false;
|
|
104
105
|
}
|
|
105
106
|
function isTimeoutError(err) {
|
|
@@ -286,7 +287,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
286
287
|
);
|
|
287
288
|
}
|
|
288
289
|
getSupportedChains() {
|
|
289
|
-
return ["evm", "btc", "sol"];
|
|
290
|
+
return ["evm", "btc", "sol", "tron"];
|
|
290
291
|
}
|
|
291
292
|
on(event, listener) {
|
|
292
293
|
this.emitter.on(event, listener);
|
|
@@ -536,16 +537,40 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
536
537
|
"Ledger requires PSBT format for BTC transaction signing. Provide params.psbt."
|
|
537
538
|
);
|
|
538
539
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
540
|
+
try {
|
|
541
|
+
const accountPath = params.inputs?.[0]?.path ? params.inputs[0].path.split("/").slice(0, 3).join("/") : void 0;
|
|
542
|
+
const result = await this.connectorCall(connectId, "btcSignTransaction", {
|
|
543
|
+
psbt: params.psbt,
|
|
544
|
+
coin: params.coin,
|
|
545
|
+
path: accountPath
|
|
546
|
+
});
|
|
547
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
548
|
+
signatures: [],
|
|
549
|
+
serializedTx: result.signedPsbt,
|
|
550
|
+
signedPsbt: result.signedPsbt
|
|
551
|
+
});
|
|
552
|
+
} catch (err) {
|
|
553
|
+
return this.errorToFailure(err);
|
|
554
|
+
}
|
|
543
555
|
}
|
|
544
|
-
async btcSignMessage(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
556
|
+
async btcSignMessage(connectId, _deviceId, params) {
|
|
557
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
558
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
|
|
559
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const result = await this.connectorCall(connectId, "btcSignMessage", {
|
|
563
|
+
path: params.path,
|
|
564
|
+
message: params.message,
|
|
565
|
+
coin: params.coin
|
|
566
|
+
});
|
|
567
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
568
|
+
signature: result.signature,
|
|
569
|
+
address: result.address || ""
|
|
570
|
+
});
|
|
571
|
+
} catch (err) {
|
|
572
|
+
return this.errorToFailure(err);
|
|
573
|
+
}
|
|
549
574
|
}
|
|
550
575
|
// ---------------------------------------------------------------------------
|
|
551
576
|
// Device fingerprint
|
|
@@ -608,18 +633,91 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
608
633
|
return this.errorToFailure(err);
|
|
609
634
|
}
|
|
610
635
|
}
|
|
611
|
-
async solSignTransaction(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
636
|
+
async solSignTransaction(connectId, _deviceId, params) {
|
|
637
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
638
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
|
|
639
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const result = await this.connectorCall(connectId, "solSignTransaction", {
|
|
643
|
+
path: params.path,
|
|
644
|
+
serializedTx: params.serializedTx
|
|
645
|
+
});
|
|
646
|
+
return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
|
|
647
|
+
} catch (err) {
|
|
648
|
+
return this.errorToFailure(err);
|
|
649
|
+
}
|
|
616
650
|
}
|
|
617
|
-
async solSignMessage(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
651
|
+
async solSignMessage(connectId, _deviceId, params) {
|
|
652
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
653
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
|
|
654
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const result = await this.connectorCall(connectId, "solSignMessage", {
|
|
658
|
+
path: params.path,
|
|
659
|
+
message: params.message
|
|
660
|
+
});
|
|
661
|
+
return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
|
|
662
|
+
} catch (err) {
|
|
663
|
+
return this.errorToFailure(err);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// ---------------------------------------------------------------------------
|
|
667
|
+
// TRON methods
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
async tronGetAddress(connectId, _deviceId, params) {
|
|
670
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
671
|
+
try {
|
|
672
|
+
const result = await this.connectorCall(connectId, "tronGetAddress", {
|
|
673
|
+
path: params.path,
|
|
674
|
+
showOnDevice: params.showOnDevice
|
|
675
|
+
});
|
|
676
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
677
|
+
address: result.address,
|
|
678
|
+
path: params.path
|
|
679
|
+
});
|
|
680
|
+
} catch (err) {
|
|
681
|
+
return this.errorToFailure(err);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async tronGetAddresses(connectId, deviceId, params, onProgress) {
|
|
685
|
+
return this.batchCall(
|
|
686
|
+
params,
|
|
687
|
+
(p) => this.tronGetAddress(connectId, deviceId, p),
|
|
688
|
+
onProgress
|
|
621
689
|
);
|
|
622
690
|
}
|
|
691
|
+
async tronSignTransaction(connectId, _deviceId, params) {
|
|
692
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
693
|
+
try {
|
|
694
|
+
if (!params.rawTxHex) {
|
|
695
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
696
|
+
import_hardware_wallet_core2.HardwareErrorCode.InvalidParams,
|
|
697
|
+
"TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const result = await this.connectorCall(connectId, "tronSignTransaction", {
|
|
701
|
+
path: params.path,
|
|
702
|
+
rawTxHex: params.rawTxHex
|
|
703
|
+
});
|
|
704
|
+
return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
|
|
705
|
+
} catch (err) {
|
|
706
|
+
return this.errorToFailure(err);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async tronSignMessage(connectId, _deviceId, params) {
|
|
710
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
711
|
+
try {
|
|
712
|
+
const result = await this.connectorCall(connectId, "tronSignMessage", {
|
|
713
|
+
path: params.path,
|
|
714
|
+
messageHex: params.message
|
|
715
|
+
});
|
|
716
|
+
return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
|
|
717
|
+
} catch (err) {
|
|
718
|
+
return this.errorToFailure(err);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
623
721
|
/**
|
|
624
722
|
* Wait for user to connect and unlock device.
|
|
625
723
|
* Emits 'ui-request' event via the adapter's own emitter.
|
|
@@ -1069,12 +1167,30 @@ var LedgerDeviceManager = class {
|
|
|
1069
1167
|
};
|
|
1070
1168
|
|
|
1071
1169
|
// src/signer/deviceActionToPromise.ts
|
|
1072
|
-
|
|
1170
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1171
|
+
function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1073
1172
|
return new Promise((resolve, reject) => {
|
|
1074
1173
|
let settled = false;
|
|
1075
1174
|
let sub;
|
|
1175
|
+
let timer = null;
|
|
1176
|
+
const resetTimer = () => {
|
|
1177
|
+
if (timer) clearTimeout(timer);
|
|
1178
|
+
if (timeoutMs > 0) {
|
|
1179
|
+
timer = setTimeout(() => {
|
|
1180
|
+
if (!settled) {
|
|
1181
|
+
settled = true;
|
|
1182
|
+
sub?.unsubscribe();
|
|
1183
|
+
reject(new Error("Device action timed out \u2014 device may be locked or disconnected"));
|
|
1184
|
+
}
|
|
1185
|
+
}, timeoutMs);
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
resetTimer();
|
|
1189
|
+
console.log("[DMK-Observable] subscribing to action.observable...");
|
|
1076
1190
|
sub = action.observable.subscribe({
|
|
1077
1191
|
next: (state) => {
|
|
1192
|
+
console.log("[DMK-Observable] next received");
|
|
1193
|
+
resetTimer();
|
|
1078
1194
|
console.log("[DMK-Observable] state:", JSON.stringify({
|
|
1079
1195
|
status: state.status,
|
|
1080
1196
|
interaction: state.intermediateValue?.requiredUserInteraction,
|
|
@@ -1085,10 +1201,14 @@ function deviceActionToPromise(action, onInteraction) {
|
|
|
1085
1201
|
if (settled) return;
|
|
1086
1202
|
if (state.status === "completed") {
|
|
1087
1203
|
settled = true;
|
|
1204
|
+
if (timer) clearTimeout(timer);
|
|
1205
|
+
onInteraction?.("interaction-complete");
|
|
1088
1206
|
sub?.unsubscribe();
|
|
1089
1207
|
resolve(state.output);
|
|
1090
1208
|
} else if (state.status === "error") {
|
|
1091
1209
|
settled = true;
|
|
1210
|
+
if (timer) clearTimeout(timer);
|
|
1211
|
+
onInteraction?.("interaction-complete");
|
|
1092
1212
|
sub?.unsubscribe();
|
|
1093
1213
|
reject(state.error);
|
|
1094
1214
|
} else if (state.status === "pending" && onInteraction) {
|
|
@@ -1103,6 +1223,7 @@ function deviceActionToPromise(action, onInteraction) {
|
|
|
1103
1223
|
error: (err) => {
|
|
1104
1224
|
if (!settled) {
|
|
1105
1225
|
settled = true;
|
|
1226
|
+
if (timer) clearTimeout(timer);
|
|
1106
1227
|
sub?.unsubscribe();
|
|
1107
1228
|
reject(err);
|
|
1108
1229
|
}
|
|
@@ -1110,6 +1231,7 @@ function deviceActionToPromise(action, onInteraction) {
|
|
|
1110
1231
|
complete: () => {
|
|
1111
1232
|
if (!settled) {
|
|
1112
1233
|
settled = true;
|
|
1234
|
+
if (timer) clearTimeout(timer);
|
|
1113
1235
|
reject(new Error("Device action completed without result"));
|
|
1114
1236
|
}
|
|
1115
1237
|
}
|
|
@@ -1126,6 +1248,7 @@ function hexToBytes(hex) {
|
|
|
1126
1248
|
}
|
|
1127
1249
|
return bytes;
|
|
1128
1250
|
}
|
|
1251
|
+
var INTERACTIVE_TIMEOUT_MS = 5 * 6e4;
|
|
1129
1252
|
var SignerEth = class {
|
|
1130
1253
|
constructor(_sdk) {
|
|
1131
1254
|
this._sdk = _sdk;
|
|
@@ -1133,22 +1256,25 @@ var SignerEth = class {
|
|
|
1133
1256
|
async getAddress(derivationPath, options) {
|
|
1134
1257
|
const checkOnDevice = options?.checkOnDevice ?? false;
|
|
1135
1258
|
console.log("[DMK] getAddress \u2192 DMK:", { derivationPath, checkOnDevice });
|
|
1259
|
+
console.log("[DMK] signer instance id:", this._instanceId ?? "none");
|
|
1136
1260
|
const action = this._sdk.getAddress(derivationPath, {
|
|
1137
1261
|
checkOnDevice
|
|
1138
1262
|
});
|
|
1139
|
-
|
|
1263
|
+
console.log("[DMK] getAddress action created:", !!action, "hasObservable:", !!action?.observable);
|
|
1264
|
+
const timeout = checkOnDevice ? INTERACTIVE_TIMEOUT_MS : void 0;
|
|
1265
|
+
return deviceActionToPromise(action, this.onInteraction, timeout);
|
|
1140
1266
|
}
|
|
1141
1267
|
async signTransaction(derivationPath, serializedTxHex) {
|
|
1142
1268
|
const action = this._sdk.signTransaction(derivationPath, hexToBytes(serializedTxHex));
|
|
1143
|
-
return deviceActionToPromise(action, this.onInteraction);
|
|
1269
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
|
|
1144
1270
|
}
|
|
1145
1271
|
async signMessage(derivationPath, message) {
|
|
1146
1272
|
const action = this._sdk.signMessage(derivationPath, message);
|
|
1147
|
-
return deviceActionToPromise(action, this.onInteraction);
|
|
1273
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
|
|
1148
1274
|
}
|
|
1149
1275
|
async signTypedData(derivationPath, data) {
|
|
1150
1276
|
const action = this._sdk.signTypedData(derivationPath, data);
|
|
1151
|
-
return deviceActionToPromise(action, this.onInteraction);
|
|
1277
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
|
|
1152
1278
|
}
|
|
1153
1279
|
};
|
|
1154
1280
|
|
|
@@ -1160,11 +1286,13 @@ var SignerManager = class _SignerManager {
|
|
|
1160
1286
|
this._builderFn = builderFn ?? _SignerManager._defaultBuilder();
|
|
1161
1287
|
}
|
|
1162
1288
|
async getOrCreate(sessionId) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1289
|
+
const hadCached = this._cache.has(sessionId);
|
|
1290
|
+
this._cache.delete(sessionId);
|
|
1291
|
+
console.log("[DMK] SignerManager.getOrCreate:", { sessionId, hadCached, creating: true });
|
|
1165
1292
|
const builder = await this._builderFn({ dmk: this._dmk, sessionId });
|
|
1166
1293
|
const sdkSigner = builder.build();
|
|
1167
|
-
|
|
1294
|
+
console.log("[DMK] SignerManager: new signer built");
|
|
1295
|
+
const signer = new SignerEth(sdkSigner);
|
|
1168
1296
|
this._cache.set(sessionId, signer);
|
|
1169
1297
|
return signer;
|
|
1170
1298
|
}
|
|
@@ -1187,6 +1315,7 @@ var SignerManager = class _SignerManager {
|
|
|
1187
1315
|
};
|
|
1188
1316
|
|
|
1189
1317
|
// src/signer/SignerBtc.ts
|
|
1318
|
+
var INTERACTIVE_TIMEOUT_MS2 = 5 * 6e4;
|
|
1190
1319
|
var SignerBtc = class {
|
|
1191
1320
|
constructor(_sdk) {
|
|
1192
1321
|
this._sdk = _sdk;
|
|
@@ -1209,6 +1338,31 @@ var SignerBtc = class {
|
|
|
1209
1338
|
const result = await deviceActionToPromise(action, this.onInteraction);
|
|
1210
1339
|
return result.masterFingerprint;
|
|
1211
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Sign a PSBT and return the array of partial signatures.
|
|
1343
|
+
* The `wallet` param is a DefaultWallet or WalletPolicy instance.
|
|
1344
|
+
* The `psbt` param can be a hex string, base64 string, or Uint8Array.
|
|
1345
|
+
*/
|
|
1346
|
+
async signPsbt(wallet, psbt, options) {
|
|
1347
|
+
const action = this._sdk.signPsbt(wallet, psbt, options);
|
|
1348
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Sign a PSBT and return the fully extracted raw transaction as a hex string.
|
|
1352
|
+
* Like signPsbt, but also finalises the PSBT and extracts the transaction.
|
|
1353
|
+
*/
|
|
1354
|
+
async signTransaction(wallet, psbt, options) {
|
|
1355
|
+
const action = this._sdk.signTransaction(wallet, psbt, options);
|
|
1356
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Sign a message with the BTC app (BIP-137 / "Bitcoin Signed Message").
|
|
1360
|
+
* Returns `{ r, s, v }` signature object.
|
|
1361
|
+
*/
|
|
1362
|
+
async signMessage(derivationPath, message, options) {
|
|
1363
|
+
const action = this._sdk.signMessage(derivationPath, message, options);
|
|
1364
|
+
return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
|
|
1365
|
+
}
|
|
1212
1366
|
};
|
|
1213
1367
|
|
|
1214
1368
|
// src/signer/SignerSol.ts
|
|
@@ -1241,6 +1395,200 @@ var SignerSol = class {
|
|
|
1241
1395
|
}
|
|
1242
1396
|
};
|
|
1243
1397
|
|
|
1398
|
+
// src/signer/SignerTron.ts
|
|
1399
|
+
var CLA = 224;
|
|
1400
|
+
var INS_ADDRESS = 2;
|
|
1401
|
+
var INS_SIGN = 4;
|
|
1402
|
+
var INS_SIGN_MESSAGE = 8;
|
|
1403
|
+
var CHUNK_SIZE = 250;
|
|
1404
|
+
function hexToBytes2(hex) {
|
|
1405
|
+
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1406
|
+
const bytes = new Uint8Array(h.length / 2);
|
|
1407
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1408
|
+
bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16);
|
|
1409
|
+
}
|
|
1410
|
+
return bytes;
|
|
1411
|
+
}
|
|
1412
|
+
function bytesToHex(bytes) {
|
|
1413
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1414
|
+
}
|
|
1415
|
+
function splitPath(path) {
|
|
1416
|
+
const p = path.startsWith("m/") ? path.slice(2) : path;
|
|
1417
|
+
return p.split("/").map((component) => {
|
|
1418
|
+
const hardened = component.endsWith("'");
|
|
1419
|
+
const index = parseInt(hardened ? component.slice(0, -1) : component, 10);
|
|
1420
|
+
return hardened ? index + 2147483648 : index;
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
function serializePath(path) {
|
|
1424
|
+
const components = splitPath(path);
|
|
1425
|
+
const buf = new Uint8Array(1 + components.length * 4);
|
|
1426
|
+
buf[0] = components.length;
|
|
1427
|
+
for (let i = 0; i < components.length; i++) {
|
|
1428
|
+
const val = components[i];
|
|
1429
|
+
const offset = 1 + i * 4;
|
|
1430
|
+
buf[offset] = val >>> 24 & 255;
|
|
1431
|
+
buf[offset + 1] = val >>> 16 & 255;
|
|
1432
|
+
buf[offset + 2] = val >>> 8 & 255;
|
|
1433
|
+
buf[offset + 3] = val & 255;
|
|
1434
|
+
}
|
|
1435
|
+
return buf;
|
|
1436
|
+
}
|
|
1437
|
+
function buildApdu(cla, ins, p1, p2, data) {
|
|
1438
|
+
const dataLen = data?.length ?? 0;
|
|
1439
|
+
const apdu = new Uint8Array(5 + dataLen);
|
|
1440
|
+
apdu[0] = cla;
|
|
1441
|
+
apdu[1] = ins;
|
|
1442
|
+
apdu[2] = p1;
|
|
1443
|
+
apdu[3] = p2;
|
|
1444
|
+
apdu[4] = dataLen;
|
|
1445
|
+
if (data && dataLen > 0) {
|
|
1446
|
+
apdu.set(data, 5);
|
|
1447
|
+
}
|
|
1448
|
+
return apdu;
|
|
1449
|
+
}
|
|
1450
|
+
function checkStatusCode(statusCode, context) {
|
|
1451
|
+
if (statusCode.length < 2) {
|
|
1452
|
+
throw new Error(`${context}: invalid status code length`);
|
|
1453
|
+
}
|
|
1454
|
+
const sw = statusCode[0] << 8 | statusCode[1];
|
|
1455
|
+
if (sw !== 36864) {
|
|
1456
|
+
throw Object.assign(
|
|
1457
|
+
new Error(`${context}: device returned error status 0x${sw.toString(16).padStart(4, "0")}`),
|
|
1458
|
+
{ statusCode: sw.toString(16), errorCode: sw.toString() }
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
var SignerTron = class {
|
|
1463
|
+
constructor(_sendApdu) {
|
|
1464
|
+
this._sendApdu = _sendApdu;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Get the TRON address at the given derivation path.
|
|
1468
|
+
*
|
|
1469
|
+
* APDU: CLA=0xE0, INS=0x02, P1=(showOnDevice?0x01:0x00), P2=0x00
|
|
1470
|
+
* Data: pathCount(1) + paths(4 bytes each, big-endian, hardened bit)
|
|
1471
|
+
* Response: pubKeyLen(1) + pubKey(pubKeyLen) + addrLen(1) + addr(addrLen, ASCII base58)
|
|
1472
|
+
*/
|
|
1473
|
+
async getAddress(path, options) {
|
|
1474
|
+
const showOnDevice = options?.checkOnDevice ?? false;
|
|
1475
|
+
const pathData = serializePath(path);
|
|
1476
|
+
const apdu = buildApdu(
|
|
1477
|
+
CLA,
|
|
1478
|
+
INS_ADDRESS,
|
|
1479
|
+
showOnDevice ? 1 : 0,
|
|
1480
|
+
0,
|
|
1481
|
+
pathData
|
|
1482
|
+
);
|
|
1483
|
+
const response = await this._sendApdu(apdu);
|
|
1484
|
+
checkStatusCode(response.statusCode, "tronGetAddress");
|
|
1485
|
+
const data = response.data;
|
|
1486
|
+
let offset = 0;
|
|
1487
|
+
const pubKeyLen = data[offset];
|
|
1488
|
+
offset += 1;
|
|
1489
|
+
const publicKey = bytesToHex(data.slice(offset, offset + pubKeyLen));
|
|
1490
|
+
offset += pubKeyLen;
|
|
1491
|
+
const addrLen = data[offset];
|
|
1492
|
+
offset += 1;
|
|
1493
|
+
const addressBytes = data.slice(offset, offset + addrLen);
|
|
1494
|
+
const address = new TextDecoder().decode(addressBytes);
|
|
1495
|
+
return { publicKey, address };
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Sign a TRON transaction (protobuf-encoded raw transaction).
|
|
1499
|
+
*
|
|
1500
|
+
* The transaction bytes are split into 250-byte chunks and sent sequentially.
|
|
1501
|
+
* First chunk includes the serialized derivation path prefix.
|
|
1502
|
+
*
|
|
1503
|
+
* P1 flags:
|
|
1504
|
+
* 0x10 = single chunk (entire tx fits in one APDU)
|
|
1505
|
+
* 0x00 = first chunk of multi-chunk
|
|
1506
|
+
* 0x80 = middle chunk (continuation)
|
|
1507
|
+
* 0x90 = last chunk (final continuation)
|
|
1508
|
+
*
|
|
1509
|
+
* Returns: 65-byte signature as hex string (no 0x prefix).
|
|
1510
|
+
*/
|
|
1511
|
+
async signTransaction(path, rawTxHex) {
|
|
1512
|
+
const pathData = serializePath(path);
|
|
1513
|
+
const txBytes = hexToBytes2(rawTxHex);
|
|
1514
|
+
const firstChunkMaxTx = CHUNK_SIZE - pathData.length;
|
|
1515
|
+
const txForFirst = txBytes.slice(0, firstChunkMaxTx);
|
|
1516
|
+
const firstPayload = new Uint8Array(pathData.length + txForFirst.length);
|
|
1517
|
+
firstPayload.set(pathData, 0);
|
|
1518
|
+
firstPayload.set(txForFirst, pathData.length);
|
|
1519
|
+
const remaining = txBytes.slice(firstChunkMaxTx);
|
|
1520
|
+
const chunks = [];
|
|
1521
|
+
let pos = 0;
|
|
1522
|
+
while (pos < remaining.length) {
|
|
1523
|
+
chunks.push(remaining.slice(pos, pos + CHUNK_SIZE));
|
|
1524
|
+
pos += CHUNK_SIZE;
|
|
1525
|
+
}
|
|
1526
|
+
const totalChunks = 1 + chunks.length;
|
|
1527
|
+
let response;
|
|
1528
|
+
if (totalChunks === 1) {
|
|
1529
|
+
const apdu = buildApdu(CLA, INS_SIGN, 16, 0, firstPayload);
|
|
1530
|
+
response = await this._sendApdu(apdu);
|
|
1531
|
+
checkStatusCode(response.statusCode, "tronSignTransaction");
|
|
1532
|
+
} else {
|
|
1533
|
+
const firstApdu = buildApdu(CLA, INS_SIGN, 0, 0, firstPayload);
|
|
1534
|
+
response = await this._sendApdu(firstApdu);
|
|
1535
|
+
checkStatusCode(response.statusCode, "tronSignTransaction (first)");
|
|
1536
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1537
|
+
const isLast = i === chunks.length - 1;
|
|
1538
|
+
const p1 = isLast ? 144 : 128;
|
|
1539
|
+
const apdu = buildApdu(CLA, INS_SIGN, p1, 0, chunks[i]);
|
|
1540
|
+
response = await this._sendApdu(apdu);
|
|
1541
|
+
checkStatusCode(response.statusCode, `tronSignTransaction (chunk ${i + 1})`);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return bytesToHex(response.data.slice(0, 65));
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Sign a personal message with the TRON app.
|
|
1548
|
+
*
|
|
1549
|
+
* First chunk: pathCount(1) + paths(4 bytes BE) + messageLength(4 bytes BE) + message bytes
|
|
1550
|
+
* Subsequent: continuation bytes
|
|
1551
|
+
* P1: 0x00 = first, 0x80 = continuation
|
|
1552
|
+
*
|
|
1553
|
+
* Returns: 65-byte signature as hex string (no 0x prefix).
|
|
1554
|
+
*/
|
|
1555
|
+
async signMessage(path, messageHex) {
|
|
1556
|
+
const pathData = serializePath(path);
|
|
1557
|
+
const messageBytes = hexToBytes2(messageHex);
|
|
1558
|
+
const msgLenBuf = new Uint8Array(4);
|
|
1559
|
+
const msgLen = messageBytes.length;
|
|
1560
|
+
msgLenBuf[0] = msgLen >>> 24 & 255;
|
|
1561
|
+
msgLenBuf[1] = msgLen >>> 16 & 255;
|
|
1562
|
+
msgLenBuf[2] = msgLen >>> 8 & 255;
|
|
1563
|
+
msgLenBuf[3] = msgLen & 255;
|
|
1564
|
+
const header = new Uint8Array(pathData.length + 4);
|
|
1565
|
+
header.set(pathData, 0);
|
|
1566
|
+
header.set(msgLenBuf, pathData.length);
|
|
1567
|
+
const firstChunkMaxMsg = CHUNK_SIZE - header.length;
|
|
1568
|
+
const msgForFirst = messageBytes.slice(0, firstChunkMaxMsg);
|
|
1569
|
+
const firstPayload = new Uint8Array(header.length + msgForFirst.length);
|
|
1570
|
+
firstPayload.set(header, 0);
|
|
1571
|
+
firstPayload.set(msgForFirst, header.length);
|
|
1572
|
+
const remaining = messageBytes.slice(firstChunkMaxMsg);
|
|
1573
|
+
const chunks = [];
|
|
1574
|
+
let pos = 0;
|
|
1575
|
+
while (pos < remaining.length) {
|
|
1576
|
+
chunks.push(remaining.slice(pos, pos + CHUNK_SIZE));
|
|
1577
|
+
pos += CHUNK_SIZE;
|
|
1578
|
+
}
|
|
1579
|
+
let response;
|
|
1580
|
+
const firstApdu = buildApdu(CLA, INS_SIGN_MESSAGE, 0, 0, firstPayload);
|
|
1581
|
+
response = await this._sendApdu(firstApdu);
|
|
1582
|
+
checkStatusCode(response.statusCode, "tronSignMessage (first)");
|
|
1583
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1584
|
+
const apdu = buildApdu(CLA, INS_SIGN_MESSAGE, 128, 0, chunks[i]);
|
|
1585
|
+
response = await this._sendApdu(apdu);
|
|
1586
|
+
checkStatusCode(response.statusCode, `tronSignMessage (chunk ${i + 1})`);
|
|
1587
|
+
}
|
|
1588
|
+
return bytesToHex(response.data.slice(0, 65));
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1244
1592
|
// src/connector/LedgerConnectorBase.ts
|
|
1245
1593
|
function normalizePath(path) {
|
|
1246
1594
|
return path.startsWith("m/") ? path.slice(2) : path;
|
|
@@ -1251,6 +1599,17 @@ function stripHex2(hex) {
|
|
|
1251
1599
|
function padHex642(hex) {
|
|
1252
1600
|
return `0x${stripHex2(hex).padStart(64, "0")}`;
|
|
1253
1601
|
}
|
|
1602
|
+
function hexToBytes3(hex) {
|
|
1603
|
+
const clean = stripHex2(hex);
|
|
1604
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
1605
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1606
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1607
|
+
}
|
|
1608
|
+
return bytes;
|
|
1609
|
+
}
|
|
1610
|
+
function bytesToHex2(bytes) {
|
|
1611
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1612
|
+
}
|
|
1254
1613
|
var LedgerConnectorBase = class {
|
|
1255
1614
|
constructor(createTransport, options) {
|
|
1256
1615
|
this._deviceManager = null;
|
|
@@ -1411,6 +1770,12 @@ var LedgerConnectorBase = class {
|
|
|
1411
1770
|
return this._solSignTransaction(sessionId, params);
|
|
1412
1771
|
case "solSignMessage":
|
|
1413
1772
|
return this._solSignMessage(sessionId, params);
|
|
1773
|
+
case "tronGetAddress":
|
|
1774
|
+
return this._tronGetAddress(sessionId, params);
|
|
1775
|
+
case "tronSignTransaction":
|
|
1776
|
+
return this._tronSignTransaction(sessionId, params);
|
|
1777
|
+
case "tronSignMessage":
|
|
1778
|
+
return this._tronSignMessage(sessionId, params);
|
|
1414
1779
|
default:
|
|
1415
1780
|
throw new Error(`LedgerConnector: unknown method "${method}"`);
|
|
1416
1781
|
}
|
|
@@ -1545,11 +1910,38 @@ var LedgerConnectorBase = class {
|
|
|
1545
1910
|
throw this._wrapError(err);
|
|
1546
1911
|
}
|
|
1547
1912
|
}
|
|
1548
|
-
async _btcSignTransaction(
|
|
1549
|
-
|
|
1913
|
+
async _btcSignTransaction(sessionId, params) {
|
|
1914
|
+
const btcSigner = await this._createBtcSigner(sessionId);
|
|
1915
|
+
try {
|
|
1916
|
+
const { DefaultWallet, DefaultDescriptorTemplate } = await import("@ledgerhq/device-signer-kit-bitcoin");
|
|
1917
|
+
const path = normalizePath(params.path || "84'/0'/0'");
|
|
1918
|
+
const purpose = path.split("/")[0]?.replace("'", "");
|
|
1919
|
+
let template = DefaultDescriptorTemplate.NATIVE_SEGWIT;
|
|
1920
|
+
if (purpose === "44") template = DefaultDescriptorTemplate.LEGACY;
|
|
1921
|
+
else if (purpose === "49")
|
|
1922
|
+
template = DefaultDescriptorTemplate.NESTED_SEGWIT;
|
|
1923
|
+
else if (purpose === "86") template = DefaultDescriptorTemplate.TAPROOT;
|
|
1924
|
+
const wallet = new DefaultWallet(path, template);
|
|
1925
|
+
const signedTxHex = await btcSigner.signTransaction(wallet, params.psbt);
|
|
1926
|
+
return { signedPsbt: stripHex2(signedTxHex) };
|
|
1927
|
+
} catch (err) {
|
|
1928
|
+
this._invalidateSession(sessionId);
|
|
1929
|
+
throw this._wrapError(err);
|
|
1930
|
+
}
|
|
1550
1931
|
}
|
|
1551
|
-
async _btcSignMessage(
|
|
1552
|
-
|
|
1932
|
+
async _btcSignMessage(sessionId, params) {
|
|
1933
|
+
const btcSigner = await this._createBtcSigner(sessionId);
|
|
1934
|
+
const path = normalizePath(params.path);
|
|
1935
|
+
try {
|
|
1936
|
+
const result = await btcSigner.signMessage(path, params.message);
|
|
1937
|
+
const rHex = stripHex2(result.r).padStart(64, "0");
|
|
1938
|
+
const sHex = stripHex2(result.s).padStart(64, "0");
|
|
1939
|
+
const vHex = result.v.toString(16).padStart(2, "0");
|
|
1940
|
+
return { signature: `${rHex}${sHex}${vHex}`, address: "" };
|
|
1941
|
+
} catch (err) {
|
|
1942
|
+
this._invalidateSession(sessionId);
|
|
1943
|
+
throw this._wrapError(err);
|
|
1944
|
+
}
|
|
1553
1945
|
}
|
|
1554
1946
|
async _btcGetMasterFingerprint(sessionId, params) {
|
|
1555
1947
|
const btcSigner = await this._createBtcSigner(sessionId);
|
|
@@ -1580,11 +1972,67 @@ var LedgerConnectorBase = class {
|
|
|
1580
1972
|
throw this._wrapError(err);
|
|
1581
1973
|
}
|
|
1582
1974
|
}
|
|
1583
|
-
async _solSignTransaction(
|
|
1584
|
-
|
|
1975
|
+
async _solSignTransaction(sessionId, params) {
|
|
1976
|
+
const solSigner = await this._createSolSigner(sessionId);
|
|
1977
|
+
const path = normalizePath(params.path);
|
|
1978
|
+
const txBytes = hexToBytes3(params.serializedTx);
|
|
1979
|
+
try {
|
|
1980
|
+
const result = await solSigner.signTransaction(path, txBytes);
|
|
1981
|
+
return { signature: bytesToHex2(result) };
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
this._invalidateSession(sessionId);
|
|
1984
|
+
throw this._wrapError(err);
|
|
1985
|
+
}
|
|
1585
1986
|
}
|
|
1586
|
-
async _solSignMessage(
|
|
1587
|
-
|
|
1987
|
+
async _solSignMessage(sessionId, params) {
|
|
1988
|
+
const solSigner = await this._createSolSigner(sessionId);
|
|
1989
|
+
const path = normalizePath(params.path);
|
|
1990
|
+
const messageBytes = hexToBytes3(params.message);
|
|
1991
|
+
try {
|
|
1992
|
+
const result = await solSigner.signMessage(path, messageBytes);
|
|
1993
|
+
return { signature: bytesToHex2(result) };
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
this._invalidateSession(sessionId);
|
|
1996
|
+
throw this._wrapError(err);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
// ---------------------------------------------------------------------------
|
|
2000
|
+
// Private -- TRON methods
|
|
2001
|
+
// ---------------------------------------------------------------------------
|
|
2002
|
+
async _tronGetAddress(sessionId, params) {
|
|
2003
|
+
const tronSigner = await this._createTronSigner(sessionId);
|
|
2004
|
+
const path = normalizePath(params.path);
|
|
2005
|
+
try {
|
|
2006
|
+
const result = await tronSigner.getAddress(path, {
|
|
2007
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
2008
|
+
});
|
|
2009
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
2010
|
+
} catch (err) {
|
|
2011
|
+
this._invalidateSession(sessionId);
|
|
2012
|
+
throw this._wrapError(err);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
async _tronSignTransaction(sessionId, params) {
|
|
2016
|
+
const tronSigner = await this._createTronSigner(sessionId);
|
|
2017
|
+
const path = normalizePath(params.path);
|
|
2018
|
+
try {
|
|
2019
|
+
const signature = await tronSigner.signTransaction(path, params.rawTxHex);
|
|
2020
|
+
return { signature };
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
this._invalidateSession(sessionId);
|
|
2023
|
+
throw this._wrapError(err);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
async _tronSignMessage(sessionId, params) {
|
|
2027
|
+
const tronSigner = await this._createTronSigner(sessionId);
|
|
2028
|
+
const path = normalizePath(params.path);
|
|
2029
|
+
try {
|
|
2030
|
+
const signature = await tronSigner.signMessage(path, params.messageHex);
|
|
2031
|
+
return { signature };
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
this._invalidateSession(sessionId);
|
|
2034
|
+
throw this._wrapError(err);
|
|
2035
|
+
}
|
|
1588
2036
|
}
|
|
1589
2037
|
// ---------------------------------------------------------------------------
|
|
1590
2038
|
// Private -- DMK / Manager lifecycle
|
|
@@ -1637,7 +2085,14 @@ var LedgerConnectorBase = class {
|
|
|
1637
2085
|
dmk,
|
|
1638
2086
|
sessionId
|
|
1639
2087
|
}).build();
|
|
1640
|
-
|
|
2088
|
+
const signer = new SignerBtc(sdkSigner);
|
|
2089
|
+
signer.onInteraction = (interaction) => {
|
|
2090
|
+
this._emit("ui-event", {
|
|
2091
|
+
type: interaction,
|
|
2092
|
+
payload: { sessionId }
|
|
2093
|
+
});
|
|
2094
|
+
};
|
|
2095
|
+
return signer;
|
|
1641
2096
|
}
|
|
1642
2097
|
async _createSolSigner(sessionId) {
|
|
1643
2098
|
const dmk = await this._getOrCreateDmk();
|
|
@@ -1648,6 +2103,14 @@ var LedgerConnectorBase = class {
|
|
|
1648
2103
|
}).build();
|
|
1649
2104
|
return new SignerSol(sdkSigner);
|
|
1650
2105
|
}
|
|
2106
|
+
async _createTronSigner(sessionId) {
|
|
2107
|
+
const dmk = await this._getOrCreateDmk();
|
|
2108
|
+
const sendApdu = async (rawApdu) => {
|
|
2109
|
+
const response = await dmk.sendApdu({ sessionId, apdu: rawApdu });
|
|
2110
|
+
return response;
|
|
2111
|
+
};
|
|
2112
|
+
return new SignerTron(sendApdu);
|
|
2113
|
+
}
|
|
1651
2114
|
_invalidateSession(sessionId) {
|
|
1652
2115
|
this._signerManager?.invalidate(sessionId);
|
|
1653
2116
|
}
|
|
@@ -1808,6 +2271,7 @@ var AppManager = class {
|
|
|
1808
2271
|
SignerEth,
|
|
1809
2272
|
SignerManager,
|
|
1810
2273
|
SignerSol,
|
|
2274
|
+
SignerTron,
|
|
1811
2275
|
clearRegistry,
|
|
1812
2276
|
deviceActionToPromise,
|
|
1813
2277
|
getTransportProvider,
|