@swapkit/wallet-hardware 4.8.1 → 4.8.2
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/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/keepkey/chains/cosmos.ts +69 -0
- package/src/keepkey/chains/evm.ts +141 -0
- package/src/keepkey/chains/mayachain.ts +98 -0
- package/src/keepkey/chains/ripple.ts +88 -0
- package/src/keepkey/chains/thorchain.ts +93 -0
- package/src/keepkey/chains/utxo.ts +364 -0
- package/src/keepkey/coins.ts +67 -0
- package/src/keepkey/index.ts +174 -0
- package/src/ledger/clients/cosmos.ts +84 -0
- package/src/ledger/clients/evm.ts +186 -0
- package/src/ledger/clients/near.ts +63 -0
- package/src/ledger/clients/sui.ts +130 -0
- package/src/ledger/clients/thorchain/common.ts +93 -0
- package/src/ledger/clients/thorchain/helpers.ts +120 -0
- package/src/ledger/clients/thorchain/index.ts +87 -0
- package/src/ledger/clients/thorchain/lib.ts +258 -0
- package/src/ledger/clients/thorchain/utils.ts +69 -0
- package/src/ledger/clients/tron.ts +85 -0
- package/src/ledger/clients/utxo-legacy-adapter.ts +71 -0
- package/src/ledger/clients/utxo-psbt.ts +145 -0
- package/src/ledger/clients/utxo.ts +359 -0
- package/src/ledger/clients/xrp.ts +50 -0
- package/src/ledger/cosmosTypes.ts +98 -0
- package/src/ledger/helpers/getLedgerAddress.ts +76 -0
- package/src/ledger/helpers/getLedgerClient.ts +124 -0
- package/src/ledger/helpers/getLedgerTransport.ts +102 -0
- package/src/ledger/helpers/index.ts +3 -0
- package/src/ledger/index.ts +546 -0
- package/src/ledger/interfaces/CosmosLedgerInterface.ts +54 -0
- package/src/ledger/types.ts +42 -0
- package/src/trezor/evmSigner.ts +210 -0
- package/src/trezor/index.ts +847 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type { KeepKeySdk } from "@keepkey/keepkey-sdk";
|
|
2
|
+
import {
|
|
3
|
+
Chain,
|
|
4
|
+
DerivationPath,
|
|
5
|
+
type DerivationPathArray,
|
|
6
|
+
derivationPathToString,
|
|
7
|
+
FeeOption,
|
|
8
|
+
type GenericTransferParams,
|
|
9
|
+
SwapKitError,
|
|
10
|
+
type UTXOChain,
|
|
11
|
+
} from "@swapkit/helpers";
|
|
12
|
+
import {
|
|
13
|
+
assertDerivationIndex,
|
|
14
|
+
createHDWalletHelpers,
|
|
15
|
+
getNetworkForChain,
|
|
16
|
+
getUTXOAccountIndexFromPath,
|
|
17
|
+
getUTXOAccountPath,
|
|
18
|
+
getUTXOAddressPath,
|
|
19
|
+
getUtxoApi,
|
|
20
|
+
stripToCashAddress,
|
|
21
|
+
type UTXOToolboxes,
|
|
22
|
+
} from "@swapkit/toolboxes/utxo";
|
|
23
|
+
import type { Transaction } from "@swapkit/utxo-signer";
|
|
24
|
+
import { bip32ToAddressNList, ChainToKeepKeyName } from "../coins";
|
|
25
|
+
|
|
26
|
+
interface KeepKeyInputObject {
|
|
27
|
+
addressNList: number[];
|
|
28
|
+
scriptType: string;
|
|
29
|
+
amount: string;
|
|
30
|
+
vout: number;
|
|
31
|
+
txid: string;
|
|
32
|
+
hex: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type KeepKeyUTXOWalletMethods = Record<string, unknown> & { address: string };
|
|
36
|
+
|
|
37
|
+
export async function utxoWalletMethods({
|
|
38
|
+
sdk,
|
|
39
|
+
chain,
|
|
40
|
+
derivationPath,
|
|
41
|
+
}: {
|
|
42
|
+
sdk: KeepKeySdk;
|
|
43
|
+
chain: Exclude<UTXOChain, typeof Chain.Zcash>;
|
|
44
|
+
derivationPath?: DerivationPathArray;
|
|
45
|
+
}): Promise<KeepKeyUTXOWalletMethods> {
|
|
46
|
+
const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo");
|
|
47
|
+
// This might not work for BCH
|
|
48
|
+
const toolbox = await getUtxoToolbox(chain);
|
|
49
|
+
const scriptType = [Chain.Bitcoin, Chain.Litecoin].includes(chain as typeof Chain.Bitcoin)
|
|
50
|
+
? ("p2wpkh" as const)
|
|
51
|
+
: ("p2pkh" as const);
|
|
52
|
+
|
|
53
|
+
const derivationPathString = derivationPath ? derivationPathToString(derivationPath) : `${DerivationPath[chain]}/0`;
|
|
54
|
+
|
|
55
|
+
const addressInfo = {
|
|
56
|
+
address_n: bip32ToAddressNList(derivationPathString),
|
|
57
|
+
coin: ChainToKeepKeyName[chain],
|
|
58
|
+
script_type: scriptType,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const walletAddress: string = (await sdk.address.utxoGetAddress(addressInfo)).address;
|
|
62
|
+
const network = getNetworkForChain(chain);
|
|
63
|
+
|
|
64
|
+
const signTransaction = async (tx: Transaction, inputs: KeepKeyInputObject[], memo = "") => {
|
|
65
|
+
const outputs: any[] = [];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
68
|
+
const output = tx.getOutput(i);
|
|
69
|
+
const address = tx.getOutputAddress(i, network);
|
|
70
|
+
const value = Number(output.amount);
|
|
71
|
+
|
|
72
|
+
if (address === walletAddress) {
|
|
73
|
+
outputs.push({
|
|
74
|
+
addressNList: addressInfo.address_n,
|
|
75
|
+
addressType: "change",
|
|
76
|
+
amount: value,
|
|
77
|
+
isChange: true,
|
|
78
|
+
scriptType,
|
|
79
|
+
});
|
|
80
|
+
} else if (address) {
|
|
81
|
+
const outputAddress = chain === Chain.BitcoinCash ? stripToCashAddress(address) : address;
|
|
82
|
+
|
|
83
|
+
if (outputAddress) {
|
|
84
|
+
outputs.push({ address: outputAddress, addressType: "spend", amount: value });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const removeNullAndEmptyObjectsFromArray = (arr: any[]) => {
|
|
90
|
+
return arr.filter((item) => item !== null && typeof item === "object" && Object.keys(item).length > 0);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const responseSign = await sdk.utxo.utxoSignTransaction({
|
|
94
|
+
coin: ChainToKeepKeyName[chain],
|
|
95
|
+
inputs,
|
|
96
|
+
opReturnData: memo,
|
|
97
|
+
outputs: removeNullAndEmptyObjectsFromArray(outputs),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return responseSign.serializedTx?.toString();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const transfer = async ({ recipient, feeOptionKey, feeRate, memo, ...rest }: GenericTransferParams) => {
|
|
104
|
+
if (!walletAddress)
|
|
105
|
+
throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "From address must be provided" });
|
|
106
|
+
if (!recipient)
|
|
107
|
+
throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Recipient address must be provided" });
|
|
108
|
+
|
|
109
|
+
const createTxMethod =
|
|
110
|
+
chain === Chain.BitcoinCash
|
|
111
|
+
? (toolbox as UTXOToolboxes["BCH"]).buildTx
|
|
112
|
+
: (toolbox as UTXOToolboxes["BTC"]).createTransaction;
|
|
113
|
+
|
|
114
|
+
const { tx, inputs: rawInputs } = await createTxMethod({
|
|
115
|
+
...rest,
|
|
116
|
+
feeRate: feeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast],
|
|
117
|
+
fetchTxHex: true,
|
|
118
|
+
memo,
|
|
119
|
+
recipient,
|
|
120
|
+
sender: walletAddress,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const inputs = rawInputs.map(({ value, index, hash, txHex }) => ({
|
|
124
|
+
//@TODO don't hardcode master, lookup on blockbook what input this is for and what path that address is!
|
|
125
|
+
addressNList: addressInfo.address_n,
|
|
126
|
+
amount: value.toString(),
|
|
127
|
+
hex: txHex || "",
|
|
128
|
+
scriptType,
|
|
129
|
+
txid: hash,
|
|
130
|
+
vout: index,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const txHex = await signTransaction(tx, inputs, memo);
|
|
134
|
+
return toolbox.broadcastTx(txHex);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const signTransactionWithMultipleInputs = async (
|
|
138
|
+
tx: Transaction,
|
|
139
|
+
inputs: Array<{
|
|
140
|
+
hash: string;
|
|
141
|
+
index: number;
|
|
142
|
+
value: number;
|
|
143
|
+
txHex?: string;
|
|
144
|
+
derivationIndex: number;
|
|
145
|
+
isChange: boolean;
|
|
146
|
+
}>,
|
|
147
|
+
memo = "",
|
|
148
|
+
) => {
|
|
149
|
+
const accountAddressN = bip32ToAddressNList(
|
|
150
|
+
derivationPath
|
|
151
|
+
? derivationPathToString(derivationPath.slice(0, 3) as DerivationPathArray)
|
|
152
|
+
: DerivationPath[chain],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
type KeepKeyOutput =
|
|
156
|
+
| { addressNList: number[]; addressType: "change"; amount: number; isChange: true; scriptType: string }
|
|
157
|
+
| { address: string; addressType: "spend"; amount: number };
|
|
158
|
+
|
|
159
|
+
const outputs: KeepKeyOutput[] = [];
|
|
160
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
161
|
+
const output = tx.getOutput(i);
|
|
162
|
+
const outputAddress = tx.getOutputAddress(i, network);
|
|
163
|
+
const value = Number(output.amount);
|
|
164
|
+
|
|
165
|
+
if (outputAddress === walletAddress) {
|
|
166
|
+
outputs.push({
|
|
167
|
+
addressNList: addressInfo.address_n,
|
|
168
|
+
addressType: "change",
|
|
169
|
+
amount: value,
|
|
170
|
+
isChange: true,
|
|
171
|
+
scriptType,
|
|
172
|
+
});
|
|
173
|
+
} else if (outputAddress) {
|
|
174
|
+
const finalAddress = chain === Chain.BitcoinCash ? stripToCashAddress(outputAddress) : outputAddress;
|
|
175
|
+
if (finalAddress) {
|
|
176
|
+
outputs.push({ address: finalAddress, addressType: "spend", amount: value });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const keepKeyInputs = inputs.map(({ hash, index: inputIndex, value, txHex, derivationIndex, isChange }) => {
|
|
182
|
+
const changePath = isChange ? 1 : 0;
|
|
183
|
+
const inputAddressN = [...accountAddressN, changePath, derivationIndex];
|
|
184
|
+
return {
|
|
185
|
+
addressNList: inputAddressN,
|
|
186
|
+
amount: value.toString(),
|
|
187
|
+
hex: txHex || "",
|
|
188
|
+
scriptType,
|
|
189
|
+
txid: hash,
|
|
190
|
+
vout: inputIndex,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const responseSign = await sdk.utxo.utxoSignTransaction({
|
|
195
|
+
coin: ChainToKeepKeyName[chain],
|
|
196
|
+
inputs: keepKeyInputs,
|
|
197
|
+
opReturnData: memo,
|
|
198
|
+
outputs: outputs.filter((item) => item !== null && typeof item === "object" && Object.keys(item).length > 0),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return responseSign.serializedTx?.toString() || "";
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const transferFromMultipleAddresses = async ({
|
|
205
|
+
utxos,
|
|
206
|
+
recipient,
|
|
207
|
+
assetValue,
|
|
208
|
+
memo,
|
|
209
|
+
feeRate,
|
|
210
|
+
feeOptionKey,
|
|
211
|
+
}: {
|
|
212
|
+
utxos: Array<{
|
|
213
|
+
hash: string;
|
|
214
|
+
index: number;
|
|
215
|
+
value: number;
|
|
216
|
+
txHex?: string;
|
|
217
|
+
derivationIndex: number;
|
|
218
|
+
isChange: boolean;
|
|
219
|
+
address: string;
|
|
220
|
+
}>;
|
|
221
|
+
recipient: string;
|
|
222
|
+
assetValue: { getBaseValue: (unit: string) => number; chain: string };
|
|
223
|
+
memo?: string;
|
|
224
|
+
feeRate?: number;
|
|
225
|
+
feeOptionKey?: (typeof FeeOption)[keyof typeof FeeOption];
|
|
226
|
+
}) => {
|
|
227
|
+
const txFeeRate = feeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast];
|
|
228
|
+
|
|
229
|
+
const createTxMethod =
|
|
230
|
+
chain === Chain.BitcoinCash
|
|
231
|
+
? (toolbox as UTXOToolboxes["BCH"]).buildTx
|
|
232
|
+
: (toolbox as UTXOToolboxes["BTC"]).createTransaction;
|
|
233
|
+
|
|
234
|
+
const { tx, inputs: selectedInputs } = await createTxMethod({
|
|
235
|
+
assetValue: assetValue as any,
|
|
236
|
+
feeRate: txFeeRate,
|
|
237
|
+
fetchTxHex: true,
|
|
238
|
+
memo,
|
|
239
|
+
recipient,
|
|
240
|
+
sender: walletAddress,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const inputsWithDerivation = selectedInputs.map(
|
|
244
|
+
(input: { hash: string; index: number; value: number; txHex?: string }) => {
|
|
245
|
+
const utxoInfo = utxos.find((u) => u.hash === input.hash && u.index === input.index);
|
|
246
|
+
return { ...input, derivationIndex: utxoInfo?.derivationIndex ?? 0, isChange: utxoInfo?.isChange ?? false };
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const signedTxHex = await signTransactionWithMultipleInputs(tx, inputsWithDerivation, memo);
|
|
251
|
+
return toolbox.broadcastTx(signedTxHex);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
async function getExtendedPublicKeyInfo({ accountIndex }: { accountIndex?: number } = {}) {
|
|
255
|
+
try {
|
|
256
|
+
const resolvedAccountPath = getUTXOAccountPath({ accountIndex, chain, derivationPath });
|
|
257
|
+
const resolvedAccountPathString = derivationPathToString(resolvedAccountPath);
|
|
258
|
+
const path = {
|
|
259
|
+
address_n: bip32ToAddressNList(resolvedAccountPathString),
|
|
260
|
+
coin: ChainToKeepKeyName[chain],
|
|
261
|
+
script_type: scriptType,
|
|
262
|
+
showDisplay: false,
|
|
263
|
+
symbol: chain.toUpperCase(),
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const responsePubkey = await sdk.system.info.getPublicKey(path);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
accountIndex: getUTXOAccountIndexFromPath(resolvedAccountPath),
|
|
270
|
+
path: resolvedAccountPathString,
|
|
271
|
+
xpub: responsePubkey.xpub,
|
|
272
|
+
};
|
|
273
|
+
} catch (error) {
|
|
274
|
+
throw new SwapKitError("wallet_keepkey_failed_to_get_public_key", {
|
|
275
|
+
chain,
|
|
276
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getExtendedPublicKey() {
|
|
282
|
+
return getExtendedPublicKeyInfo();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function deriveAddressAtIndex({
|
|
286
|
+
accountIndex,
|
|
287
|
+
index,
|
|
288
|
+
change = false,
|
|
289
|
+
}: {
|
|
290
|
+
accountIndex?: number;
|
|
291
|
+
index: number;
|
|
292
|
+
change?: boolean;
|
|
293
|
+
}) {
|
|
294
|
+
try {
|
|
295
|
+
assertDerivationIndex("index", index);
|
|
296
|
+
const fullPath = getUTXOAddressPath({ accountIndex, chain, change, derivationPath, index });
|
|
297
|
+
const fullPathString = derivationPathToString(fullPath);
|
|
298
|
+
|
|
299
|
+
const result = await sdk.address.utxoGetAddress({
|
|
300
|
+
address_n: bip32ToAddressNList(fullPathString),
|
|
301
|
+
coin: ChainToKeepKeyName[chain],
|
|
302
|
+
script_type: scriptType,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
let finalAddress = result.address;
|
|
306
|
+
if (chain === Chain.BitcoinCash) {
|
|
307
|
+
finalAddress = stripToCashAddress(result.address);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
accountIndex: getUTXOAccountIndexFromPath(fullPath),
|
|
312
|
+
address: finalAddress,
|
|
313
|
+
change,
|
|
314
|
+
index,
|
|
315
|
+
path: fullPathString,
|
|
316
|
+
pubkey: "",
|
|
317
|
+
};
|
|
318
|
+
} catch {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function deriveAddresses({
|
|
324
|
+
accountIndex,
|
|
325
|
+
count,
|
|
326
|
+
startIndex = 0,
|
|
327
|
+
change = false,
|
|
328
|
+
}: {
|
|
329
|
+
accountIndex?: number;
|
|
330
|
+
count: number;
|
|
331
|
+
startIndex?: number;
|
|
332
|
+
change?: boolean;
|
|
333
|
+
}) {
|
|
334
|
+
assertDerivationIndex("count", count);
|
|
335
|
+
assertDerivationIndex("startIndex", startIndex);
|
|
336
|
+
|
|
337
|
+
const addresses = await Promise.all(
|
|
338
|
+
Array.from({ length: count }, (_, i) => deriveAddressAtIndex({ accountIndex, change, index: startIndex + i })),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return addresses.filter((address) => !!address);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const hdHelpers = createHDWalletHelpers({
|
|
345
|
+
chain,
|
|
346
|
+
deriveAddress: deriveAddressAtIndex,
|
|
347
|
+
getBalance: toolbox.getBalance,
|
|
348
|
+
getUtxos: (address: string) => getUtxoApi(chain).getUtxos({ address, fetchTxHex: true }),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
...toolbox,
|
|
353
|
+
...hdHelpers,
|
|
354
|
+
address: walletAddress,
|
|
355
|
+
deriveAddressAtIndex,
|
|
356
|
+
deriveAddresses,
|
|
357
|
+
getExtendedPublicKey,
|
|
358
|
+
getExtendedPublicKeyInfo,
|
|
359
|
+
signTransaction,
|
|
360
|
+
signTransactionWithMultipleInputs,
|
|
361
|
+
transfer,
|
|
362
|
+
transferFromMultipleAddresses,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
KeepKey Specific bip32 path conventions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SwapKitError } from "@swapkit/helpers";
|
|
6
|
+
|
|
7
|
+
const HARDENED = 0x80000000;
|
|
8
|
+
|
|
9
|
+
export enum ChainToKeepKeyName {
|
|
10
|
+
BTC = "Bitcoin",
|
|
11
|
+
BCH = "BitcoinCash",
|
|
12
|
+
DOGE = "Dogecoin",
|
|
13
|
+
LTC = "Litecoin",
|
|
14
|
+
DASH = "Dash",
|
|
15
|
+
XRP = "Ripple",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function addressNListToBIP32(address: number[]) {
|
|
19
|
+
return `m/${address.map((num) => (num >= HARDENED ? `${num - HARDENED}'` : num)).join("/")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function bip32Like(path: string) {
|
|
23
|
+
if (path === "m/") return true;
|
|
24
|
+
|
|
25
|
+
return /^m(((\/[0-9]+h)+|(\/[0-9]+H)+|(\/[0-9]+')*)((\/[0-9]+)*))$/.test(path);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function bip32ToAddressNList(initPath: string): number[] {
|
|
29
|
+
let path = initPath;
|
|
30
|
+
|
|
31
|
+
if (!bip32Like(path)) {
|
|
32
|
+
throw new SwapKitError("wallet_keepkey_invalid_params", { reason: `Not a bip32 path: '${path}'` });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (/^m\//i.test(path)) {
|
|
36
|
+
path = path.slice(2);
|
|
37
|
+
}
|
|
38
|
+
const segments = path.split("/");
|
|
39
|
+
|
|
40
|
+
if (segments.length === 1 && segments[0] === "") return [];
|
|
41
|
+
|
|
42
|
+
const ret = new Array(segments.length);
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < segments.length; i++) {
|
|
45
|
+
// TODO: Check for better way instead of exec
|
|
46
|
+
const segment = segments[i];
|
|
47
|
+
if (segment) {
|
|
48
|
+
const tmp = /(\d+)([hH']?)/.exec(segment);
|
|
49
|
+
if (tmp === null) throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Invalid input" });
|
|
50
|
+
|
|
51
|
+
const [, num = "", modifier = ""] = tmp;
|
|
52
|
+
|
|
53
|
+
ret[i] = Number.parseInt(num, 10);
|
|
54
|
+
|
|
55
|
+
if (ret[i] >= HARDENED)
|
|
56
|
+
throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Invalid child index" });
|
|
57
|
+
|
|
58
|
+
if (modifier === "h" || modifier === "H" || modifier === "'") {
|
|
59
|
+
ret[i] += HARDENED;
|
|
60
|
+
} else if (modifier.length > 0) {
|
|
61
|
+
throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Invalid modifier" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return ret;
|
|
67
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { KeepKeySdk } from "@keepkey/keepkey-sdk";
|
|
2
|
+
import {
|
|
3
|
+
Chain,
|
|
4
|
+
type DerivationPathArray,
|
|
5
|
+
filterSupportedChains,
|
|
6
|
+
NetworkDerivationPath,
|
|
7
|
+
SKConfig,
|
|
8
|
+
SwapKitError,
|
|
9
|
+
WalletOption,
|
|
10
|
+
} from "@swapkit/helpers";
|
|
11
|
+
|
|
12
|
+
export type { PairingInfo } from "@keepkey/keepkey-sdk";
|
|
13
|
+
|
|
14
|
+
import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core";
|
|
15
|
+
import { cosmosWalletMethods } from "./chains/cosmos";
|
|
16
|
+
import { KeepKeySigner } from "./chains/evm";
|
|
17
|
+
import { mayachainWalletMethods } from "./chains/mayachain";
|
|
18
|
+
import { thorchainWalletMethods } from "./chains/thorchain";
|
|
19
|
+
import { utxoWalletMethods } from "./chains/utxo";
|
|
20
|
+
|
|
21
|
+
export const keepkeyWallet = createWallet({
|
|
22
|
+
connect: ({ addChain, supportedChains, walletType }) =>
|
|
23
|
+
async function connectKeepkey(chains: Chain[], derivationPathMap?: Record<Chain, DerivationPathArray>) {
|
|
24
|
+
const filteredChains = filterSupportedChains({ chains, supportedChains, walletType });
|
|
25
|
+
const pairingInfo = SKConfig.get("integrations").keepKey;
|
|
26
|
+
if (!pairingInfo) throw new Error("KeepKey config not found");
|
|
27
|
+
|
|
28
|
+
const initialApiKey = SKConfig.get("apiKeys").keepKey || "1234";
|
|
29
|
+
|
|
30
|
+
await checkAndLaunch();
|
|
31
|
+
|
|
32
|
+
// Conform to the expected { apiKey, pairingInfo } structure
|
|
33
|
+
const keepkeyConfig = { apiKey: initialApiKey, pairingInfo };
|
|
34
|
+
const keepKeySdk = await KeepKeySdk.create(keepkeyConfig);
|
|
35
|
+
|
|
36
|
+
// Persist the new API key via SKConfig after pairing
|
|
37
|
+
if (keepkeyConfig.apiKey && keepkeyConfig.apiKey !== initialApiKey) {
|
|
38
|
+
SKConfig.setApiKey("keepKey", keepkeyConfig.apiKey);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await Promise.all(
|
|
42
|
+
filteredChains.map(async (chain) => {
|
|
43
|
+
const walletMethods = await getWalletMethods({
|
|
44
|
+
chain,
|
|
45
|
+
derivationPath: derivationPathMap?.[chain] || NetworkDerivationPath[chain],
|
|
46
|
+
sdk: keepKeySdk,
|
|
47
|
+
});
|
|
48
|
+
const address = (await walletMethods.getAddress()) || "";
|
|
49
|
+
|
|
50
|
+
addChain({ ...walletMethods, address, chain, walletType: WalletOption.KEEPKEY });
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
directSigningSupport: {
|
|
56
|
+
[Chain.Arbitrum]: true,
|
|
57
|
+
[Chain.Avalanche]: true,
|
|
58
|
+
[Chain.Base]: true,
|
|
59
|
+
[Chain.Berachain]: true,
|
|
60
|
+
[Chain.BinanceSmartChain]: true,
|
|
61
|
+
[Chain.Ethereum]: true,
|
|
62
|
+
[Chain.Gnosis]: true,
|
|
63
|
+
[Chain.Monad]: true,
|
|
64
|
+
[Chain.Optimism]: true,
|
|
65
|
+
[Chain.Polygon]: true,
|
|
66
|
+
[Chain.Ripple]: true,
|
|
67
|
+
[Chain.XLayer]: true,
|
|
68
|
+
// BTC/BCH/DASH/DOGE/LTC/Cosmos/THORChain/Maya: pending KeepKey SDK signer wrappers (V3 plan PRs)
|
|
69
|
+
},
|
|
70
|
+
name: "connectKeepkey",
|
|
71
|
+
supportedChains: [
|
|
72
|
+
Chain.Arbitrum,
|
|
73
|
+
Chain.Avalanche,
|
|
74
|
+
Chain.Base,
|
|
75
|
+
Chain.Berachain,
|
|
76
|
+
Chain.BinanceSmartChain,
|
|
77
|
+
Chain.Bitcoin,
|
|
78
|
+
Chain.BitcoinCash,
|
|
79
|
+
Chain.Cosmos,
|
|
80
|
+
Chain.Dogecoin,
|
|
81
|
+
Chain.Dash,
|
|
82
|
+
Chain.Ethereum,
|
|
83
|
+
Chain.Gnosis,
|
|
84
|
+
Chain.Litecoin,
|
|
85
|
+
Chain.Monad,
|
|
86
|
+
Chain.Ripple,
|
|
87
|
+
Chain.Optimism,
|
|
88
|
+
Chain.Polygon,
|
|
89
|
+
Chain.THORChain,
|
|
90
|
+
Chain.Maya,
|
|
91
|
+
Chain.XLayer,
|
|
92
|
+
],
|
|
93
|
+
walletType: WalletOption.KEEPKEY,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const KEEPKEY_SUPPORTED_CHAINS = getWalletSupportedChains(keepkeyWallet);
|
|
97
|
+
|
|
98
|
+
async function getWalletMethods({
|
|
99
|
+
sdk,
|
|
100
|
+
chain,
|
|
101
|
+
derivationPath,
|
|
102
|
+
}: {
|
|
103
|
+
sdk: KeepKeySdk;
|
|
104
|
+
chain: Chain;
|
|
105
|
+
derivationPath?: DerivationPathArray;
|
|
106
|
+
}) {
|
|
107
|
+
const { getProvider, getEvmToolboxAsync } = await import("@swapkit/toolboxes/evm");
|
|
108
|
+
|
|
109
|
+
switch (chain) {
|
|
110
|
+
case Chain.BinanceSmartChain:
|
|
111
|
+
case Chain.Arbitrum:
|
|
112
|
+
case Chain.Berachain:
|
|
113
|
+
case Chain.Optimism:
|
|
114
|
+
case Chain.Polygon:
|
|
115
|
+
case Chain.Avalanche:
|
|
116
|
+
case Chain.Base:
|
|
117
|
+
case Chain.Ethereum:
|
|
118
|
+
case Chain.Gnosis:
|
|
119
|
+
case Chain.Monad:
|
|
120
|
+
case Chain.XLayer: {
|
|
121
|
+
const provider = await getProvider(chain);
|
|
122
|
+
const signer = new KeepKeySigner({ chain, derivationPath, provider, sdk });
|
|
123
|
+
const toolbox = await getEvmToolboxAsync(chain, { provider, signer });
|
|
124
|
+
|
|
125
|
+
return toolbox;
|
|
126
|
+
}
|
|
127
|
+
case Chain.Cosmos: {
|
|
128
|
+
return cosmosWalletMethods({ derivationPath, sdk });
|
|
129
|
+
}
|
|
130
|
+
case Chain.THORChain: {
|
|
131
|
+
return thorchainWalletMethods({ derivationPath, sdk });
|
|
132
|
+
}
|
|
133
|
+
case Chain.Maya: {
|
|
134
|
+
return mayachainWalletMethods({ derivationPath, sdk });
|
|
135
|
+
}
|
|
136
|
+
case Chain.Bitcoin:
|
|
137
|
+
case Chain.BitcoinCash:
|
|
138
|
+
case Chain.Dash:
|
|
139
|
+
case Chain.Dogecoin:
|
|
140
|
+
case Chain.Litecoin: {
|
|
141
|
+
return utxoWalletMethods({ chain, derivationPath, sdk });
|
|
142
|
+
}
|
|
143
|
+
case Chain.Ripple: {
|
|
144
|
+
const { rippleWalletMethods } = await import("./chains/ripple");
|
|
145
|
+
return rippleWalletMethods({ derivationPath, sdk });
|
|
146
|
+
}
|
|
147
|
+
default:
|
|
148
|
+
throw new SwapKitError("wallet_keepkey_chain_not_supported", { chain });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// kk-sdk docs: https://keepkey.com/blog/building_on_the_keepkey_sdk
|
|
153
|
+
// test spec: if offline, launch keepkey-bridge
|
|
154
|
+
async function checkAndLaunch(attempts = 0) {
|
|
155
|
+
if (attempts >= 3) {
|
|
156
|
+
alert("KeepKey desktop is required for keepkey-sdk, please go to https://keepkey.com/get-started");
|
|
157
|
+
}
|
|
158
|
+
const isAvailable = await checkKeepkeyAvailability();
|
|
159
|
+
|
|
160
|
+
if (!isAvailable) {
|
|
161
|
+
window.location.assign("keepkey://launch");
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
163
|
+
await checkAndLaunch(attempts + 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function checkKeepkeyAvailability(spec = "http://localhost:1646/spec/swagger.json") {
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch(spec);
|
|
170
|
+
return response.status === 200;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DerivationPathArray,
|
|
3
|
+
derivationPathToString,
|
|
4
|
+
NetworkDerivationPath,
|
|
5
|
+
SwapKitError,
|
|
6
|
+
} from "@swapkit/helpers";
|
|
7
|
+
import { CosmosLedgerInterface } from "../interfaces/CosmosLedgerInterface";
|
|
8
|
+
|
|
9
|
+
export class CosmosLedger extends CosmosLedgerInterface {
|
|
10
|
+
private pubKey: string | null = null;
|
|
11
|
+
|
|
12
|
+
derivationPath: string;
|
|
13
|
+
|
|
14
|
+
constructor(derivationPath: DerivationPathArray = NetworkDerivationPath.GAIA) {
|
|
15
|
+
super();
|
|
16
|
+
this.chain = "cosmos";
|
|
17
|
+
this.derivationPath = derivationPathToString(derivationPath);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connect = async () => {
|
|
21
|
+
await this.checkOrCreateTransportAndLedger(true);
|
|
22
|
+
const { publicKey, address } = await this.getAddressAndPubKey();
|
|
23
|
+
|
|
24
|
+
this.pubKey = Buffer.from(publicKey, "hex").toString("base64");
|
|
25
|
+
|
|
26
|
+
return address;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
getAddressAndPubKey = async () => {
|
|
30
|
+
await this.checkOrCreateTransportAndLedger(true);
|
|
31
|
+
|
|
32
|
+
const response = await this.ledgerApp.getAddress(this.derivationPath, this.chain);
|
|
33
|
+
|
|
34
|
+
return response;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
signTransaction = async (rawTx: string, sequence = "0") => {
|
|
38
|
+
await this.checkOrCreateTransportAndLedger(true);
|
|
39
|
+
|
|
40
|
+
const { return_code, error_message, signature } = await this.ledgerApp.sign(this.derivationPath, rawTx);
|
|
41
|
+
|
|
42
|
+
if (!this.pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
|
|
43
|
+
|
|
44
|
+
this.validateResponse(return_code, error_message);
|
|
45
|
+
|
|
46
|
+
return [{ pub_key: { type: "tendermint/PubKeySecp256k1", value: this.pubKey }, sequence, signature }];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
signAmino = async (signerAddress: string, signDoc: any): Promise<any> => {
|
|
50
|
+
await this.checkOrCreateTransportAndLedger(true);
|
|
51
|
+
|
|
52
|
+
const accounts = await this.getAccounts();
|
|
53
|
+
const accountIndex = accounts.findIndex((account) => account.address === signerAddress);
|
|
54
|
+
|
|
55
|
+
if (accountIndex === -1) {
|
|
56
|
+
throw new SwapKitError("wallet_ledger_address_not_found", { address: signerAddress });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const importedAmino = await import("@cosmjs/amino");
|
|
60
|
+
const encodeSecp256k1Signature =
|
|
61
|
+
importedAmino.encodeSecp256k1Signature ?? importedAmino.default?.encodeSecp256k1Signature;
|
|
62
|
+
const serializeSignDoc = importedAmino.serializeSignDoc ?? importedAmino.default?.serializeSignDoc;
|
|
63
|
+
const importedCrypto = await import("@cosmjs/crypto");
|
|
64
|
+
const Secp256k1Signature = importedCrypto.Secp256k1Signature ?? importedCrypto.default?.Secp256k1Signature;
|
|
65
|
+
|
|
66
|
+
const message = serializeSignDoc(signDoc);
|
|
67
|
+
const signature = await this.ledgerApp.sign(this.derivationPath, message);
|
|
68
|
+
|
|
69
|
+
this.validateResponse(signature.return_code, signature.error_message);
|
|
70
|
+
|
|
71
|
+
const secpSignature = Secp256k1Signature.fromDer(signature.signature).toFixedLength();
|
|
72
|
+
|
|
73
|
+
return { signature: encodeSecp256k1Signature(accounts[0].pubkey, secpSignature), signed: signDoc };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
getAccounts = async () => {
|
|
77
|
+
await this.checkOrCreateTransportAndLedger(true);
|
|
78
|
+
|
|
79
|
+
const addressAndPubKey = await this.getAddressAndPubKey();
|
|
80
|
+
return [
|
|
81
|
+
{ address: addressAndPubKey.address, algo: "secp256k1", pubkey: Buffer.from(addressAndPubKey.publicKey, "hex") },
|
|
82
|
+
] as any[];
|
|
83
|
+
};
|
|
84
|
+
}
|