otx-btc-wallet-connectors 0.1.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 +554 -0
- package/dist/base-IAFq7sd8.d.mts +53 -0
- package/dist/base-IAFq7sd8.d.ts +53 -0
- package/dist/binance/index.d.mts +81 -0
- package/dist/binance/index.d.ts +81 -0
- package/dist/binance/index.js +13 -0
- package/dist/binance/index.js.map +1 -0
- package/dist/binance/index.mjs +4 -0
- package/dist/binance/index.mjs.map +1 -0
- package/dist/bitget/index.d.mts +84 -0
- package/dist/bitget/index.d.ts +84 -0
- package/dist/bitget/index.js +13 -0
- package/dist/bitget/index.js.map +1 -0
- package/dist/bitget/index.mjs +4 -0
- package/dist/bitget/index.mjs.map +1 -0
- package/dist/chunk-5Z5Q2Y75.mjs +91 -0
- package/dist/chunk-5Z5Q2Y75.mjs.map +1 -0
- package/dist/chunk-7KK2LZLZ.mjs +208 -0
- package/dist/chunk-7KK2LZLZ.mjs.map +1 -0
- package/dist/chunk-AW2JZIHR.mjs +753 -0
- package/dist/chunk-AW2JZIHR.mjs.map +1 -0
- package/dist/chunk-EIJOSZXZ.js +331 -0
- package/dist/chunk-EIJOSZXZ.js.map +1 -0
- package/dist/chunk-EQHR7P7G.js +541 -0
- package/dist/chunk-EQHR7P7G.js.map +1 -0
- package/dist/chunk-EWRXLZO4.mjs +539 -0
- package/dist/chunk-EWRXLZO4.mjs.map +1 -0
- package/dist/chunk-FISNQZZ7.js +802 -0
- package/dist/chunk-FISNQZZ7.js.map +1 -0
- package/dist/chunk-HL4WDMGS.js +200 -0
- package/dist/chunk-HL4WDMGS.js.map +1 -0
- package/dist/chunk-IPYWR76I.js +314 -0
- package/dist/chunk-IPYWR76I.js.map +1 -0
- package/dist/chunk-JYYNWR5G.js +142 -0
- package/dist/chunk-JYYNWR5G.js.map +1 -0
- package/dist/chunk-LNKTYZJM.js +701 -0
- package/dist/chunk-LNKTYZJM.js.map +1 -0
- package/dist/chunk-LVZMONQL.mjs +699 -0
- package/dist/chunk-LVZMONQL.mjs.map +1 -0
- package/dist/chunk-MFXLQWOE.js +93 -0
- package/dist/chunk-MFXLQWOE.js.map +1 -0
- package/dist/chunk-NBIA4TTE.mjs +204 -0
- package/dist/chunk-NBIA4TTE.mjs.map +1 -0
- package/dist/chunk-O4DD2XJ2.js +206 -0
- package/dist/chunk-O4DD2XJ2.js.map +1 -0
- package/dist/chunk-P7HVBU2B.mjs +140 -0
- package/dist/chunk-P7HVBU2B.mjs.map +1 -0
- package/dist/chunk-Q7QVQYEB.js +210 -0
- package/dist/chunk-Q7QVQYEB.js.map +1 -0
- package/dist/chunk-RLZEG6KL.mjs +329 -0
- package/dist/chunk-RLZEG6KL.mjs.map +1 -0
- package/dist/chunk-SYLDBJ75.mjs +246 -0
- package/dist/chunk-SYLDBJ75.mjs.map +1 -0
- package/dist/chunk-TTEUU3CI.mjs +198 -0
- package/dist/chunk-TTEUU3CI.mjs.map +1 -0
- package/dist/chunk-V66BXDTR.mjs +292 -0
- package/dist/chunk-V66BXDTR.mjs.map +1 -0
- package/dist/chunk-X77ZT4OI.js +268 -0
- package/dist/chunk-X77ZT4OI.js.map +1 -0
- package/dist/imtoken/index.d.mts +116 -0
- package/dist/imtoken/index.d.ts +116 -0
- package/dist/imtoken/index.js +14 -0
- package/dist/imtoken/index.js.map +1 -0
- package/dist/imtoken/index.mjs +5 -0
- package/dist/imtoken/index.mjs.map +1 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +170 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ledger/index.d.mts +290 -0
- package/dist/ledger/index.d.ts +290 -0
- package/dist/ledger/index.js +14 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/index.mjs +5 -0
- package/dist/ledger/index.mjs.map +1 -0
- package/dist/okx/index.d.mts +88 -0
- package/dist/okx/index.d.ts +88 -0
- package/dist/okx/index.js +13 -0
- package/dist/okx/index.js.map +1 -0
- package/dist/okx/index.mjs +4 -0
- package/dist/okx/index.mjs.map +1 -0
- package/dist/phantom/index.d.mts +96 -0
- package/dist/phantom/index.d.ts +96 -0
- package/dist/phantom/index.js +14 -0
- package/dist/phantom/index.js.map +1 -0
- package/dist/phantom/index.mjs +5 -0
- package/dist/phantom/index.mjs.map +1 -0
- package/dist/psbt-builder-CFOs69Z5.d.mts +131 -0
- package/dist/psbt-builder-CFOs69Z5.d.ts +131 -0
- package/dist/trezor/index.d.mts +155 -0
- package/dist/trezor/index.d.ts +155 -0
- package/dist/trezor/index.js +14 -0
- package/dist/trezor/index.js.map +1 -0
- package/dist/trezor/index.mjs +5 -0
- package/dist/trezor/index.mjs.map +1 -0
- package/dist/unisat/index.d.mts +75 -0
- package/dist/unisat/index.d.ts +75 -0
- package/dist/unisat/index.js +13 -0
- package/dist/unisat/index.js.map +1 -0
- package/dist/unisat/index.mjs +4 -0
- package/dist/unisat/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +398 -0
- package/dist/utils/index.d.ts +398 -0
- package/dist/utils/index.js +120 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +3 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/xverse/index.d.mts +79 -0
- package/dist/xverse/index.d.ts +79 -0
- package/dist/xverse/index.js +13 -0
- package/dist/xverse/index.js.map +1 -0
- package/dist/xverse/index.mjs +4 -0
- package/dist/xverse/index.mjs.map +1 -0
- package/package.json +108 -0
- package/src/base.ts +132 -0
- package/src/binance/BinanceConnector.ts +307 -0
- package/src/binance/index.ts +1 -0
- package/src/bitget/BitgetConnector.ts +301 -0
- package/src/bitget/index.ts +1 -0
- package/src/imtoken/ImTokenConnector.ts +420 -0
- package/src/imtoken/index.ts +2 -0
- package/src/index.ts +78 -0
- package/src/ledger/LedgerConnector.ts +1019 -0
- package/src/ledger/index.ts +8 -0
- package/src/okx/OKXConnector.ts +230 -0
- package/src/okx/index.ts +1 -0
- package/src/phantom/PhantomConnector.ts +381 -0
- package/src/phantom/index.ts +2 -0
- package/src/trezor/TrezorConnector.ts +824 -0
- package/src/trezor/index.ts +6 -0
- package/src/unisat/UnisatConnector.ts +312 -0
- package/src/unisat/index.ts +1 -0
- package/src/utils/blockstream.ts +230 -0
- package/src/utils/btc-service.ts +364 -0
- package/src/utils/index.ts +56 -0
- package/src/utils/mempool.ts +232 -0
- package/src/utils/psbt-builder.ts +492 -0
- package/src/utils/types.ts +183 -0
- package/src/xverse/XverseConnector.ts +479 -0
- package/src/xverse/index.ts +1 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import { BtcService, getAddressType, getInputVBytes, getOutputVBytes, getDustThreshold } from './chunk-AW2JZIHR.mjs';
|
|
2
|
+
import { BaseConnector } from './chunk-5Z5Q2Y75.mjs';
|
|
3
|
+
|
|
4
|
+
// src/ledger/LedgerConnector.ts
|
|
5
|
+
var LEDGER_ICON = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83MDM1XzUxMTApIj4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHJ4PSIzMiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE0Mi4yMzkgMzcuNUg4NS4wOTY0VjExNC41ODJIMTYyLjIzOVY1Ny40MzkzQzE2Mi4yMzggNTQuODE2IDE2MS43MiA1Mi4yMTg2IDE2MC43MTQgNDkuNzk1OUMxNTkuNzA4IDQ3LjM3MzIgMTU4LjIzMyA0NS4xNzI4IDE1Ni4zNzYgNDMuMzIwN0MxNTQuNTE4IDQxLjQ2ODUgMTUyLjMxMyA0MC4wMDExIDE0OS44ODcgMzkuMDAyM0MxNDcuNDYyIDM4LjAwMzUgMTQ0Ljg2MyAzNy40OTMgMTQyLjIzOSAzNy41WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTY3LjI5MjkgMzcuNUg1Ny40NDY0QzUyLjE1NjMgMzcuNSA0Ny4wODI5IDM5LjYwMTUgNDMuMzQyMiA0My4zNDIyQzM5LjYwMTUgNDcuMDgyOSAzNy41IDUyLjE1NjMgMzcuNSA1Ny40NDY0TDM3LjUgNjcuMjkyOUg2Ny4yOTI5VjM3LjVaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMzcuNSA4NS4xMDM2SDY3LjI5MjlWMTE0Ljg5NkgzNy41Vjg1LjEwMzZaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMTMyLjcwNyAxNjIuMjQ2SDE0Mi41NTNDMTQ1LjE3NyAxNjIuMjQ2IDE0Ny43NzYgMTYxLjcyOSAxNTAuMTk5IDE2MC43MjNDMTUyLjYyMyAxNTkuNzE3IDE1NC44MjQgMTU4LjI0MyAxNTYuNjc3IDE1Ni4zODVDMTU4LjUzIDE1NC41MjcgMTU5Ljk5OCAxNTIuMzIyIDE2MC45OTcgMTQ5Ljg5NkMxNjEuOTk2IDE0Ny40NyAxNjIuNTA3IDE0NC44NyAxNjIuNSAxNDIuMjQ2VjEzMi43MDdIMTMyLjcwN1YxNjIuMjQ2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTg1LjEwMzUgMTMyLjcwN0gxMTQuODk2VjE2Mi41SDg1LjEwMzVWMTMyLjcwN1oiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik0zNy41IDEzMi43MDdWMTQyLjU1NEMzNy41IDE0NS4xNzMgMzguMDE1OSAxNDcuNzY3IDM5LjAxODMgMTUwLjE4N0M0MC4wMjA3IDE1Mi42MDcgNDEuNDkgMTU0LjgwNiA0My4zNDIyIDE1Ni42NThDNDUuMTk0NCAxNTguNTEgNDcuMzkzMiAxNTkuOTc5IDQ5LjgxMzMgMTYwLjk4MkM1Mi4yMzMzIDE2MS45ODQgNTQuODI3IDE2Mi41IDU3LjQ0NjQgMTYyLjVINjcuMjkyOVYxMzIuNzA3SDM3LjVaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzcwMzVfNTExMCI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=";
|
|
6
|
+
var ADDRESS_TYPE_TO_PATH = {
|
|
7
|
+
"legacy": 44,
|
|
8
|
+
"nested-segwit": 49,
|
|
9
|
+
"segwit": 84,
|
|
10
|
+
"taproot": 86
|
|
11
|
+
};
|
|
12
|
+
var ADDRESS_TYPE_TO_FORMAT = {
|
|
13
|
+
"legacy": "legacy",
|
|
14
|
+
"nested-segwit": "p2sh",
|
|
15
|
+
"segwit": "bech32",
|
|
16
|
+
"taproot": "bech32m"
|
|
17
|
+
};
|
|
18
|
+
var _LedgerConnector = class _LedgerConnector extends BaseConnector {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
super();
|
|
21
|
+
this.id = "ledger";
|
|
22
|
+
this.name = "Ledger";
|
|
23
|
+
this.icon = LEDGER_ICON;
|
|
24
|
+
this._transport = null;
|
|
25
|
+
this._btcApp = null;
|
|
26
|
+
this._account = null;
|
|
27
|
+
this._network = "mainnet";
|
|
28
|
+
this._derivationPath = "";
|
|
29
|
+
this._options = {
|
|
30
|
+
addressType: options.addressType ?? "segwit",
|
|
31
|
+
accountIndex: options.accountIndex ?? 0,
|
|
32
|
+
addressIndex: options.addressIndex ?? 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Override checkReady - Ledger is always "ready" since it doesn't require browser extension
|
|
37
|
+
* The actual device connection happens when connect() is called
|
|
38
|
+
*/
|
|
39
|
+
checkReady() {
|
|
40
|
+
this.ready = true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the provider - for Ledger this is the Bitcoin app instance
|
|
44
|
+
*/
|
|
45
|
+
getProvider() {
|
|
46
|
+
return this._btcApp;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get current address type
|
|
50
|
+
*/
|
|
51
|
+
getAddressType() {
|
|
52
|
+
return this._options.addressType;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set address type options before connecting
|
|
56
|
+
* Call this before connect() to change the address derivation path
|
|
57
|
+
*/
|
|
58
|
+
setOptions(options) {
|
|
59
|
+
this._options = {
|
|
60
|
+
addressType: options.addressType ?? this._options.addressType,
|
|
61
|
+
accountIndex: options.accountIndex ?? this._options.accountIndex,
|
|
62
|
+
addressIndex: options.addressIndex ?? this._options.addressIndex
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get available address types for UI selection
|
|
67
|
+
*/
|
|
68
|
+
static getAvailableAddressTypes() {
|
|
69
|
+
return [
|
|
70
|
+
{ type: "legacy", label: "Legacy (P2PKH)", description: "Starts with '1' or 'm/n'" },
|
|
71
|
+
{ type: "nested-segwit", label: "Nested SegWit (P2SH-P2WPKH)", description: "Starts with '3' or '2'" },
|
|
72
|
+
{ type: "segwit", label: "Native SegWit (P2WPKH)", description: "Starts with 'bc1q' or 'tb1q'" },
|
|
73
|
+
{ type: "taproot", label: "Taproot (P2TR)", description: "Starts with 'bc1p' or 'tb1p'" }
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if WebUSB is supported in the current browser
|
|
78
|
+
*/
|
|
79
|
+
async isSupported() {
|
|
80
|
+
if (typeof window === "undefined")
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
const TransportWebUSB = (await import('@ledgerhq/hw-transport-webusb')).default;
|
|
84
|
+
return await TransportWebUSB.isSupported();
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Initialize transport with timeout
|
|
91
|
+
* Uses request() instead of create() to always show device selection popup
|
|
92
|
+
*/
|
|
93
|
+
async initTransport() {
|
|
94
|
+
console.log("[LedgerConnector] initTransport() called");
|
|
95
|
+
await this.closeTransport();
|
|
96
|
+
console.log("[LedgerConnector] Importing TransportWebUSB...");
|
|
97
|
+
const TransportWebUSB = (await import('@ledgerhq/hw-transport-webusb')).default;
|
|
98
|
+
console.log("[LedgerConnector] TransportWebUSB imported");
|
|
99
|
+
console.log("[LedgerConnector] Requesting device selection...");
|
|
100
|
+
const transportPromise = TransportWebUSB.request();
|
|
101
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
reject(new Error("Connection timeout. Please make sure your Ledger is connected and unlocked."));
|
|
104
|
+
}, _LedgerConnector.CONNECTION_TIMEOUT);
|
|
105
|
+
});
|
|
106
|
+
const transport = await Promise.race([transportPromise, timeoutPromise]);
|
|
107
|
+
console.log("[LedgerConnector] Transport created successfully");
|
|
108
|
+
return transport;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get BTC app instance, initializing if needed
|
|
112
|
+
*/
|
|
113
|
+
async getBtcAppInstance() {
|
|
114
|
+
console.log("[LedgerConnector] getBtcAppInstance() called");
|
|
115
|
+
if (this._btcApp) {
|
|
116
|
+
console.log("[LedgerConnector] Returning existing BTC app instance");
|
|
117
|
+
return this._btcApp;
|
|
118
|
+
}
|
|
119
|
+
const transport = await this.initTransport();
|
|
120
|
+
this._transport = transport;
|
|
121
|
+
console.log("[LedgerConnector] Checking if Bitcoin app is open...");
|
|
122
|
+
try {
|
|
123
|
+
await transport.send(176, 1, 0, 0);
|
|
124
|
+
console.log("[LedgerConnector] Bitcoin app is open");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("[LedgerConnector] Bitcoin app check failed:", error);
|
|
127
|
+
await this.closeTransport();
|
|
128
|
+
throw new Error("Please open the Bitcoin app on your Ledger device");
|
|
129
|
+
}
|
|
130
|
+
console.log("[LedgerConnector] Importing AppBtc...");
|
|
131
|
+
const AppBtc = (await import('@ledgerhq/hw-app-btc')).default;
|
|
132
|
+
console.log("[LedgerConnector] AppBtc imported");
|
|
133
|
+
const currency = this._network === "mainnet" ? "bitcoin" : "bitcoin_testnet";
|
|
134
|
+
this._btcApp = new AppBtc({ transport, currency });
|
|
135
|
+
console.log("[LedgerConnector] BTC app instance created");
|
|
136
|
+
return this._btcApp;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Connect to Ledger device
|
|
140
|
+
* IMPORTANT: This must be called within a user gesture (click event)
|
|
141
|
+
*/
|
|
142
|
+
async connect(network = "mainnet") {
|
|
143
|
+
console.log("[LedgerConnector] connect() called with network:", network);
|
|
144
|
+
try {
|
|
145
|
+
console.log("[LedgerConnector] Checking WebUSB support...");
|
|
146
|
+
const supported = await this.isSupported();
|
|
147
|
+
console.log("[LedgerConnector] WebUSB supported:", supported);
|
|
148
|
+
if (!supported) {
|
|
149
|
+
throw new Error("WebUSB is not supported in this browser. Please use Chrome or a Chromium-based browser.");
|
|
150
|
+
}
|
|
151
|
+
this._network = network;
|
|
152
|
+
console.log("[LedgerConnector] Initializing BTC app...");
|
|
153
|
+
const btcApp = await this.getBtcAppInstance();
|
|
154
|
+
console.log("[LedgerConnector] BTC app initialized");
|
|
155
|
+
const coinType = network === "mainnet" ? 0 : 1;
|
|
156
|
+
const purpose = ADDRESS_TYPE_TO_PATH[this._options.addressType];
|
|
157
|
+
this._derivationPath = `${purpose}'/${coinType}'/${this._options.accountIndex}'/0/${this._options.addressIndex}`;
|
|
158
|
+
console.log("[LedgerConnector] Derivation path:", this._derivationPath);
|
|
159
|
+
const format = ADDRESS_TYPE_TO_FORMAT[this._options.addressType];
|
|
160
|
+
console.log("[LedgerConnector] Getting wallet public key...");
|
|
161
|
+
const result = await btcApp.getWalletPublicKey(this._derivationPath, {
|
|
162
|
+
verify: false,
|
|
163
|
+
format
|
|
164
|
+
});
|
|
165
|
+
console.log("[LedgerConnector] Got wallet public key:", result.bitcoinAddress);
|
|
166
|
+
this._account = {
|
|
167
|
+
address: result.bitcoinAddress,
|
|
168
|
+
publicKey: result.publicKey,
|
|
169
|
+
type: this.mapLedgerAddressType(this._options.addressType)
|
|
170
|
+
};
|
|
171
|
+
this.setupDisconnectListener();
|
|
172
|
+
console.log("[LedgerConnector] Connection successful!");
|
|
173
|
+
return this._account;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("[LedgerConnector] Connection error:", error);
|
|
176
|
+
await this.closeTransport();
|
|
177
|
+
this.handleLedgerError(error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get address with verification on device
|
|
182
|
+
*/
|
|
183
|
+
async getAddressWithVerification() {
|
|
184
|
+
if (!this._btcApp) {
|
|
185
|
+
throw new Error("Not connected to Ledger device");
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const format = ADDRESS_TYPE_TO_FORMAT[this._options.addressType];
|
|
189
|
+
const result = await this._btcApp.getWalletPublicKey(this._derivationPath, {
|
|
190
|
+
verify: true,
|
|
191
|
+
// Show address on device for verification
|
|
192
|
+
format
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
address: result.bitcoinAddress,
|
|
196
|
+
publicKey: result.publicKey,
|
|
197
|
+
type: this.mapLedgerAddressType(this._options.addressType)
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
this.handleLedgerError(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
setupDisconnectListener() {
|
|
204
|
+
if (!this._transport)
|
|
205
|
+
return;
|
|
206
|
+
this._transport.on("disconnect", () => {
|
|
207
|
+
this._transport = null;
|
|
208
|
+
this._btcApp = null;
|
|
209
|
+
this._account = null;
|
|
210
|
+
this.emitAccountsChanged([]);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async closeTransport() {
|
|
214
|
+
if (this._transport) {
|
|
215
|
+
try {
|
|
216
|
+
await this._transport.close();
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
this._transport = null;
|
|
220
|
+
this._btcApp = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async disconnect() {
|
|
224
|
+
await this.closeTransport();
|
|
225
|
+
this._account = null;
|
|
226
|
+
this.cleanup();
|
|
227
|
+
}
|
|
228
|
+
async getAccounts() {
|
|
229
|
+
if (!this._account) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
return [this._account];
|
|
233
|
+
}
|
|
234
|
+
async signMessage(message) {
|
|
235
|
+
if (!this._btcApp) {
|
|
236
|
+
throw new Error("Not connected to Ledger device");
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const messageHex = Buffer.from(message, "utf8").toString("hex");
|
|
240
|
+
const result = await this._btcApp.signMessage(this._derivationPath, messageHex);
|
|
241
|
+
const v = result.v;
|
|
242
|
+
const r = result.r;
|
|
243
|
+
const s = result.s;
|
|
244
|
+
const vHex = v.toString(16).padStart(2, "0");
|
|
245
|
+
const signature = Buffer.from(vHex + r + s, "hex");
|
|
246
|
+
return signature.toString("base64");
|
|
247
|
+
} catch (error) {
|
|
248
|
+
this.handleLedgerError(error);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Sign a PSBT with Ledger
|
|
253
|
+
*
|
|
254
|
+
* Note: Ledger hardware wallets don't support direct PSBT signing through the standard API.
|
|
255
|
+
* Use the createTransaction method or sendTransaction instead.
|
|
256
|
+
*/
|
|
257
|
+
async signPsbt(_psbtHex, _options) {
|
|
258
|
+
throw new Error("SignPsbt is not supported for Ledger Wallet. Use createTransaction or sendTransaction instead.");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Sign multiple PSBTs
|
|
262
|
+
*/
|
|
263
|
+
async signPsbts(psbtHexs, options) {
|
|
264
|
+
const results = [];
|
|
265
|
+
for (const psbtHex of psbtHexs) {
|
|
266
|
+
const signed = await this.signPsbt(psbtHex, options);
|
|
267
|
+
results.push(signed);
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Send a Bitcoin transaction
|
|
273
|
+
*
|
|
274
|
+
* @param to - Recipient address
|
|
275
|
+
* @param satoshis - Amount to send in satoshis
|
|
276
|
+
* @param options - Send options (feeRate, etc.)
|
|
277
|
+
* @returns Transaction ID after broadcast
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* const txid = await ledger.sendTransaction('bc1q...', 50000);
|
|
282
|
+
* // With custom fee rate
|
|
283
|
+
* const txid = await ledger.sendTransaction('bc1q...', 50000, { feeRate: 10 });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
async sendTransaction(to, satoshis, options) {
|
|
287
|
+
if (!this._btcApp || !this._account) {
|
|
288
|
+
throw new Error("Not connected to Ledger device");
|
|
289
|
+
}
|
|
290
|
+
const btcService = new BtcService(this._network);
|
|
291
|
+
const utxos = await btcService.getUtxosWithTxHex(this._account.address);
|
|
292
|
+
if (utxos.length === 0) {
|
|
293
|
+
throw new Error("No UTXOs available for spending");
|
|
294
|
+
}
|
|
295
|
+
let feeRate = options?.feeRate;
|
|
296
|
+
if (!feeRate) {
|
|
297
|
+
const feeRates = await btcService.getFeeRates();
|
|
298
|
+
feeRate = feeRates.hour;
|
|
299
|
+
}
|
|
300
|
+
const fromAddressType = getAddressType(this._account.address);
|
|
301
|
+
const toAddressType = getAddressType(to);
|
|
302
|
+
const inputVBytes = getInputVBytes(fromAddressType);
|
|
303
|
+
const outputVBytes = getOutputVBytes(toAddressType);
|
|
304
|
+
const changeOutputVBytes = getOutputVBytes(fromAddressType);
|
|
305
|
+
const baseVBytes = 10.5;
|
|
306
|
+
const sortedUtxos = [...utxos].sort((a, b) => b.value - a.value);
|
|
307
|
+
const selectedUtxos = [];
|
|
308
|
+
let totalInputValue = 0;
|
|
309
|
+
for (const utxo of sortedUtxos) {
|
|
310
|
+
selectedUtxos.push(utxo);
|
|
311
|
+
totalInputValue += utxo.value;
|
|
312
|
+
const estimatedVBytes2 = baseVBytes + selectedUtxos.length * inputVBytes + outputVBytes + changeOutputVBytes;
|
|
313
|
+
const estimatedFee2 = Math.ceil(estimatedVBytes2 * feeRate);
|
|
314
|
+
if (totalInputValue >= satoshis + estimatedFee2) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
let estimatedVBytes = baseVBytes + selectedUtxos.length * inputVBytes + outputVBytes + changeOutputVBytes;
|
|
319
|
+
let estimatedFee = Math.ceil(estimatedVBytes * feeRate);
|
|
320
|
+
if (totalInputValue < satoshis + estimatedFee) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Insufficient funds. Available: ${totalInputValue} sats, Required: ${satoshis + estimatedFee} sats (including fee)`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
let changeAmount = totalInputValue - satoshis - estimatedFee;
|
|
326
|
+
const outputs = [{ address: to, satoshis }];
|
|
327
|
+
const dustThreshold = getDustThreshold(fromAddressType);
|
|
328
|
+
if (changeAmount > dustThreshold) {
|
|
329
|
+
outputs.push({ address: this._account.address, satoshis: changeAmount });
|
|
330
|
+
} else {
|
|
331
|
+
estimatedVBytes = baseVBytes + selectedUtxos.length * inputVBytes + outputVBytes;
|
|
332
|
+
estimatedFee = Math.ceil(estimatedVBytes * feeRate);
|
|
333
|
+
changeAmount = 0;
|
|
334
|
+
if (totalInputValue < satoshis + estimatedFee) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Insufficient funds after fee adjustment. Available: ${totalInputValue} sats, Required: ${satoshis + estimatedFee} sats`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const inputs = selectedUtxos.map((utxo) => ({
|
|
341
|
+
txHex: utxo.txHex,
|
|
342
|
+
outputIndex: utxo.vout,
|
|
343
|
+
derivationPath: this._derivationPath
|
|
344
|
+
}));
|
|
345
|
+
const signedTxHex = await this.createTransaction(inputs, outputs);
|
|
346
|
+
const txid = await btcService.broadcastTransaction(signedTxHex);
|
|
347
|
+
return txid;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Create and sign a Bitcoin transaction using Ledger
|
|
351
|
+
*
|
|
352
|
+
* This method requires you to provide UTXOs (unspent transaction outputs) which
|
|
353
|
+
* must be fetched from a blockchain API (e.g., Blockstream, Mempool.space, etc.)
|
|
354
|
+
*
|
|
355
|
+
* @param inputs - Array of UTXO inputs to spend
|
|
356
|
+
* @param outputs - Array of outputs (recipients)
|
|
357
|
+
* @param changePath - Optional BIP32 path for change output
|
|
358
|
+
* @returns Signed transaction hex ready for broadcast
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* const signedTx = await ledger.createTransaction(
|
|
363
|
+
* [{
|
|
364
|
+
* txHex: '0100000001...', // Raw tx hex that created the UTXO
|
|
365
|
+
* outputIndex: 0,
|
|
366
|
+
* derivationPath: "84'/0'/0'/0/0",
|
|
367
|
+
* }],
|
|
368
|
+
* [{
|
|
369
|
+
* address: 'bc1q...',
|
|
370
|
+
* satoshis: 50000,
|
|
371
|
+
* }],
|
|
372
|
+
* "84'/0'/0'/1/0" // Change address path
|
|
373
|
+
* );
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
async createTransaction(inputs, outputs, changePath) {
|
|
377
|
+
if (!this._btcApp) {
|
|
378
|
+
throw new Error("Not connected to Ledger device");
|
|
379
|
+
}
|
|
380
|
+
if (inputs.length === 0) {
|
|
381
|
+
throw new Error("At least one input is required");
|
|
382
|
+
}
|
|
383
|
+
if (outputs.length === 0) {
|
|
384
|
+
throw new Error("At least one output is required");
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const isSegwit = this._options.addressType !== "legacy";
|
|
388
|
+
const additionals = this.getAdditionals();
|
|
389
|
+
const parsedInputs = [];
|
|
390
|
+
const associatedKeysets = [];
|
|
391
|
+
for (const input of inputs) {
|
|
392
|
+
const tx = this._btcApp.splitTransaction(input.txHex, isSegwit, false, additionals);
|
|
393
|
+
parsedInputs.push([
|
|
394
|
+
tx,
|
|
395
|
+
input.outputIndex,
|
|
396
|
+
input.redeemScript ?? null,
|
|
397
|
+
input.sequence ?? 4294967295
|
|
398
|
+
]);
|
|
399
|
+
associatedKeysets.push(input.derivationPath);
|
|
400
|
+
}
|
|
401
|
+
const outputScriptHex = this.buildOutputScript(outputs);
|
|
402
|
+
const txArgs = {
|
|
403
|
+
inputs: parsedInputs,
|
|
404
|
+
associatedKeysets,
|
|
405
|
+
outputScriptHex,
|
|
406
|
+
segwit: isSegwit,
|
|
407
|
+
additionals,
|
|
408
|
+
useTrustedInputForSegwit: isSegwit
|
|
409
|
+
};
|
|
410
|
+
if (changePath) {
|
|
411
|
+
txArgs.changePath = changePath;
|
|
412
|
+
}
|
|
413
|
+
const signedTx = await this._btcApp.createPaymentTransaction(txArgs);
|
|
414
|
+
return signedTx;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
this.handleLedgerError(error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Build output script hex from outputs array
|
|
421
|
+
* Format: varint(output_count) + [amount(8 bytes LE) + varint(script_len) + script]...
|
|
422
|
+
*/
|
|
423
|
+
buildOutputScript(outputs) {
|
|
424
|
+
let script = "";
|
|
425
|
+
script += this.encodeVarint(outputs.length);
|
|
426
|
+
for (const output of outputs) {
|
|
427
|
+
const amountHex = this.uint64ToLittleEndian(output.satoshis);
|
|
428
|
+
script += amountHex;
|
|
429
|
+
const scriptPubKey = this.addressToScriptPubKey(output.address);
|
|
430
|
+
script += this.encodeVarint(scriptPubKey.length / 2);
|
|
431
|
+
script += scriptPubKey;
|
|
432
|
+
}
|
|
433
|
+
return script;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Convert address to scriptPubKey hex
|
|
437
|
+
*/
|
|
438
|
+
addressToScriptPubKey(address) {
|
|
439
|
+
console.log("[LedgerConnector] addressToScriptPubKey called with:", address);
|
|
440
|
+
if (address.startsWith("bc1q") || address.startsWith("tb1q")) {
|
|
441
|
+
const decoded = this.decodeBech32(address);
|
|
442
|
+
console.log("[LedgerConnector] P2WPKH decoded length:", decoded.length, "hex:", decoded);
|
|
443
|
+
if (decoded.length !== 40) {
|
|
444
|
+
throw new Error(`Invalid P2WPKH address: expected 40 hex chars, got ${decoded.length}`);
|
|
445
|
+
}
|
|
446
|
+
return "0014" + decoded;
|
|
447
|
+
} else if (address.startsWith("bc1p") || address.startsWith("tb1p")) {
|
|
448
|
+
const decoded = this.decodeBech32(address);
|
|
449
|
+
console.log("[LedgerConnector] P2TR decoded length:", decoded.length, "hex:", decoded);
|
|
450
|
+
if (decoded.length !== 64) {
|
|
451
|
+
throw new Error(`Invalid P2TR address: expected 64 hex chars, got ${decoded.length}`);
|
|
452
|
+
}
|
|
453
|
+
return "5120" + decoded;
|
|
454
|
+
} else if (address.startsWith("3") || address.startsWith("2")) {
|
|
455
|
+
const decoded = this.decodeBase58Check(address);
|
|
456
|
+
return "a914" + decoded + "87";
|
|
457
|
+
} else if (address.startsWith("1") || address.startsWith("m") || address.startsWith("n")) {
|
|
458
|
+
const decoded = this.decodeBase58Check(address);
|
|
459
|
+
return "76a914" + decoded + "88ac";
|
|
460
|
+
} else {
|
|
461
|
+
throw new Error(`Unsupported address format: ${address}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Decode bech32/bech32m address to hex
|
|
466
|
+
*/
|
|
467
|
+
decodeBech32(address) {
|
|
468
|
+
const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
469
|
+
const pos = address.lastIndexOf("1");
|
|
470
|
+
if (pos < 1)
|
|
471
|
+
throw new Error("Invalid bech32 address");
|
|
472
|
+
const data = address.slice(pos + 1);
|
|
473
|
+
const values = [];
|
|
474
|
+
for (const char of data) {
|
|
475
|
+
const idx = CHARSET.indexOf(char.toLowerCase());
|
|
476
|
+
if (idx === -1)
|
|
477
|
+
throw new Error("Invalid bech32 character");
|
|
478
|
+
values.push(idx);
|
|
479
|
+
}
|
|
480
|
+
const dataValues = values.slice(0, -6);
|
|
481
|
+
const witnessData = dataValues.slice(1);
|
|
482
|
+
let acc = 0;
|
|
483
|
+
let bits = 0;
|
|
484
|
+
const result = [];
|
|
485
|
+
for (const value of witnessData) {
|
|
486
|
+
acc = acc << 5 | value;
|
|
487
|
+
bits += 5;
|
|
488
|
+
while (bits >= 8) {
|
|
489
|
+
bits -= 8;
|
|
490
|
+
result.push(acc >> bits & 255);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return Buffer.from(result).toString("hex");
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Decode base58check address to hex (without version byte)
|
|
497
|
+
*/
|
|
498
|
+
decodeBase58Check(address) {
|
|
499
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
500
|
+
let num = BigInt(0);
|
|
501
|
+
for (const char of address) {
|
|
502
|
+
const idx = ALPHABET.indexOf(char);
|
|
503
|
+
if (idx === -1)
|
|
504
|
+
throw new Error("Invalid base58 character");
|
|
505
|
+
num = num * BigInt(58) + BigInt(idx);
|
|
506
|
+
}
|
|
507
|
+
let hex = num.toString(16);
|
|
508
|
+
if (hex.length % 2 !== 0)
|
|
509
|
+
hex = "0" + hex;
|
|
510
|
+
let leadingZeros = 0;
|
|
511
|
+
for (const char of address) {
|
|
512
|
+
if (char === "1")
|
|
513
|
+
leadingZeros++;
|
|
514
|
+
else
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
hex = "00".repeat(leadingZeros) + hex;
|
|
518
|
+
return hex.slice(2, -8);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Encode number as varint hex
|
|
522
|
+
*/
|
|
523
|
+
encodeVarint(n) {
|
|
524
|
+
if (n < 253) {
|
|
525
|
+
return n.toString(16).padStart(2, "0");
|
|
526
|
+
} else if (n <= 65535) {
|
|
527
|
+
return "fd" + this.uint16ToLittleEndian(n);
|
|
528
|
+
} else if (n <= 4294967295) {
|
|
529
|
+
return "fe" + this.uint32ToLittleEndian(n);
|
|
530
|
+
} else {
|
|
531
|
+
return "ff" + this.uint64ToLittleEndian(n);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Convert uint16 to little-endian hex
|
|
536
|
+
*/
|
|
537
|
+
uint16ToLittleEndian(n) {
|
|
538
|
+
const buf = Buffer.alloc(2);
|
|
539
|
+
buf.writeUInt16LE(n);
|
|
540
|
+
return buf.toString("hex");
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Convert uint32 to little-endian hex
|
|
544
|
+
*/
|
|
545
|
+
uint32ToLittleEndian(n) {
|
|
546
|
+
const buf = Buffer.alloc(4);
|
|
547
|
+
buf.writeUInt32LE(n);
|
|
548
|
+
return buf.toString("hex");
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Convert uint64 to little-endian hex
|
|
552
|
+
*/
|
|
553
|
+
uint64ToLittleEndian(n) {
|
|
554
|
+
const buf = Buffer.alloc(8);
|
|
555
|
+
buf.writeBigUInt64LE(BigInt(n));
|
|
556
|
+
return buf.toString("hex");
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get additionals array based on address type
|
|
560
|
+
*/
|
|
561
|
+
getAdditionals() {
|
|
562
|
+
switch (this._options.addressType) {
|
|
563
|
+
case "segwit":
|
|
564
|
+
return ["bech32"];
|
|
565
|
+
case "taproot":
|
|
566
|
+
return ["bech32m"];
|
|
567
|
+
case "nested-segwit":
|
|
568
|
+
return [];
|
|
569
|
+
case "legacy":
|
|
570
|
+
default:
|
|
571
|
+
return [];
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async getNetwork() {
|
|
575
|
+
return this._network;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get extended public key (xpub/ypub/zpub) for account
|
|
579
|
+
*/
|
|
580
|
+
async getExtendedPublicKey() {
|
|
581
|
+
if (!this._btcApp) {
|
|
582
|
+
throw new Error("Not connected to Ledger device");
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const coinType = this._network === "mainnet" ? 0 : 1;
|
|
586
|
+
const purpose = ADDRESS_TYPE_TO_PATH[this._options.addressType];
|
|
587
|
+
const accountPath = `${purpose}'/${coinType}'/${this._options.accountIndex}'`;
|
|
588
|
+
const xpubVersion = this.getXpubVersion();
|
|
589
|
+
const xpub = await this._btcApp.getWalletXpub({
|
|
590
|
+
path: accountPath,
|
|
591
|
+
xpubVersion
|
|
592
|
+
});
|
|
593
|
+
return xpub;
|
|
594
|
+
} catch (error) {
|
|
595
|
+
this.handleLedgerError(error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Get multiple addresses for the account
|
|
600
|
+
*/
|
|
601
|
+
async getAddresses(startIndex, count) {
|
|
602
|
+
if (!this._btcApp) {
|
|
603
|
+
throw new Error("Not connected to Ledger device");
|
|
604
|
+
}
|
|
605
|
+
const accounts = [];
|
|
606
|
+
const coinType = this._network === "mainnet" ? 0 : 1;
|
|
607
|
+
const purpose = ADDRESS_TYPE_TO_PATH[this._options.addressType];
|
|
608
|
+
const format = ADDRESS_TYPE_TO_FORMAT[this._options.addressType];
|
|
609
|
+
for (let i = startIndex; i < startIndex + count; i++) {
|
|
610
|
+
const path = `${purpose}'/${coinType}'/${this._options.accountIndex}'/0/${i}`;
|
|
611
|
+
const result = await this._btcApp.getWalletPublicKey(path, {
|
|
612
|
+
verify: false,
|
|
613
|
+
format
|
|
614
|
+
});
|
|
615
|
+
accounts.push({
|
|
616
|
+
address: result.bitcoinAddress,
|
|
617
|
+
publicKey: result.publicKey,
|
|
618
|
+
type: this.mapLedgerAddressType(this._options.addressType)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return accounts;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Get current derivation path
|
|
625
|
+
*/
|
|
626
|
+
getDerivationPath() {
|
|
627
|
+
return this._derivationPath;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Check if device is connected
|
|
631
|
+
*/
|
|
632
|
+
isConnected() {
|
|
633
|
+
return this._transport !== null && this._btcApp !== null;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get the Ledger Bitcoin app instance for advanced operations
|
|
637
|
+
* This allows users to call createPaymentTransaction directly
|
|
638
|
+
*/
|
|
639
|
+
getBtcApp() {
|
|
640
|
+
return this._btcApp;
|
|
641
|
+
}
|
|
642
|
+
mapLedgerAddressType(addressType) {
|
|
643
|
+
switch (addressType) {
|
|
644
|
+
case "legacy":
|
|
645
|
+
return "legacy";
|
|
646
|
+
case "nested-segwit":
|
|
647
|
+
return "nested-segwit";
|
|
648
|
+
case "segwit":
|
|
649
|
+
return "segwit";
|
|
650
|
+
case "taproot":
|
|
651
|
+
return "taproot";
|
|
652
|
+
default:
|
|
653
|
+
return "segwit";
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
getXpubVersion() {
|
|
657
|
+
const isMainnet = this._network === "mainnet";
|
|
658
|
+
switch (this._options.addressType) {
|
|
659
|
+
case "legacy":
|
|
660
|
+
return isMainnet ? 76067358 : 70617039;
|
|
661
|
+
case "nested-segwit":
|
|
662
|
+
return isMainnet ? 77429938 : 71979618;
|
|
663
|
+
case "segwit":
|
|
664
|
+
return isMainnet ? 78792518 : 73342198;
|
|
665
|
+
case "taproot":
|
|
666
|
+
return isMainnet ? 76067358 : 70617039;
|
|
667
|
+
default:
|
|
668
|
+
return isMainnet ? 78792518 : 73342198;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
handleLedgerError(error) {
|
|
672
|
+
if (error instanceof Error) {
|
|
673
|
+
const message = error.message.toLowerCase();
|
|
674
|
+
if (message.includes("locked") || message.includes("0x6982")) {
|
|
675
|
+
throw new Error("Ledger device is locked. Please unlock it and try again.");
|
|
676
|
+
}
|
|
677
|
+
if (message.includes("denied") || message.includes("rejected") || message.includes("0x6985")) {
|
|
678
|
+
throw new Error("User rejected the request on Ledger device.");
|
|
679
|
+
}
|
|
680
|
+
if (message.includes("app") && message.includes("open")) {
|
|
681
|
+
throw new Error("Please open the Bitcoin app on your Ledger device.");
|
|
682
|
+
}
|
|
683
|
+
if (message.includes("transportopenusercancelled")) {
|
|
684
|
+
throw new Error("Connection cancelled. Please try again.");
|
|
685
|
+
}
|
|
686
|
+
if (message.includes("no device selected")) {
|
|
687
|
+
throw new Error("No Ledger device selected. Please connect your device and try again.");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
this.handleError(error);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
// Connection timeout in milliseconds
|
|
694
|
+
_LedgerConnector.CONNECTION_TIMEOUT = 3e4;
|
|
695
|
+
var LedgerConnector = _LedgerConnector;
|
|
696
|
+
|
|
697
|
+
export { LedgerConnector };
|
|
698
|
+
//# sourceMappingURL=out.js.map
|
|
699
|
+
//# sourceMappingURL=chunk-LVZMONQL.mjs.map
|