@swapkit/wallet-hardware 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD_PARTY_LICENSES.md +2344 -0
- package/dist/chunk-09e51wma.js +4 -0
- package/dist/chunk-09e51wma.js.map +167 -0
- package/dist/chunk-0arpsm5f.js +4 -0
- package/dist/chunk-0arpsm5f.js.map +10 -0
- package/dist/chunk-1amf58aq.js +4 -0
- package/dist/{chunk-vwv6z2mk.js.map → chunk-1amf58aq.js.map} +2 -2
- package/dist/chunk-3t17wpjg.js +17 -0
- package/dist/chunk-3t17wpjg.js.map +29 -0
- package/dist/chunk-5c7b7z2d.js +4 -0
- package/dist/{chunk-9gy0xve4.js.map → chunk-5c7b7z2d.js.map} +2 -2
- package/dist/chunk-5p3ttw6y.js +4 -0
- package/dist/{chunk-n05bv2n5.js.map → chunk-5p3ttw6y.js.map} +1 -1
- package/dist/chunk-dptbnkzm.js +64 -0
- package/dist/chunk-dptbnkzm.js.map +260 -0
- package/dist/chunk-ds2m83jp.js +5 -0
- package/dist/chunk-ds2m83jp.js.map +11 -0
- package/dist/chunk-eenx3cv6.js +4 -0
- package/dist/chunk-eenx3cv6.js.map +33 -0
- package/dist/chunk-gr3eswf1.js +4 -0
- package/dist/chunk-gr3eswf1.js.map +73 -0
- package/dist/{chunk-7fsaymh4.js → chunk-hg71hp4m.js} +3 -3
- package/dist/{chunk-7fsaymh4.js.map → chunk-hg71hp4m.js.map} +1 -1
- package/dist/chunk-jewm2ag3.js +5 -0
- package/dist/chunk-jewm2ag3.js.map +10 -0
- package/dist/chunk-jzrytcrf.js +4 -0
- package/dist/chunk-jzrytcrf.js.map +12 -0
- package/dist/chunk-k3thje6a.js +4 -0
- package/dist/chunk-k3thje6a.js.map +9 -0
- package/dist/chunk-nhva847d.js +5 -0
- package/dist/chunk-nhva847d.js.map +9 -0
- package/dist/chunk-tm47brht.js +4 -0
- package/dist/chunk-tm47brht.js.map +140 -0
- package/dist/chunk-xmvg4cds.js +4 -0
- package/dist/chunk-xmvg4cds.js.map +127 -0
- package/dist/chunk-xx7cwpzq.js +4 -0
- package/dist/chunk-xx7cwpzq.js.map +9 -0
- package/dist/chunk-yrxe491r.js +20 -0
- package/dist/chunk-yrxe491r.js.map +17 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/keepkey/index.cjs +2 -2
- package/dist/keepkey/index.cjs.map +2 -2
- package/dist/keepkey/index.js +2 -2
- package/dist/keepkey/index.js.map +2 -2
- package/dist/ledger/index.cjs +90 -2
- package/dist/ledger/index.cjs.map +830 -23
- package/dist/ledger/index.js +2 -2
- package/dist/ledger/index.js.map +21 -19
- package/dist/trezor/index.cjs +2 -2
- package/dist/trezor/index.cjs.map +5 -4
- package/dist/trezor/index.js +2 -2
- package/dist/trezor/index.js.map +3 -3
- package/dist/types/helpers/psbt.d.ts +9 -0
- package/dist/types/helpers/psbt.d.ts.map +1 -0
- package/dist/types/ledger/clients/bitcoin.d.ts +36 -0
- package/dist/types/ledger/clients/bitcoin.d.ts.map +1 -0
- package/dist/types/ledger/clients/cosmos.d.ts +38 -10
- package/dist/types/ledger/clients/cosmos.d.ts.map +1 -1
- package/dist/types/ledger/clients/evm.d.ts +37 -46
- package/dist/types/ledger/clients/evm.d.ts.map +1 -1
- package/dist/types/ledger/clients/near.d.ts +5 -2
- package/dist/types/ledger/clients/near.d.ts.map +1 -1
- package/dist/types/ledger/clients/sui.d.ts +8 -12
- package/dist/types/ledger/clients/sui.d.ts.map +1 -1
- package/dist/types/ledger/clients/thorchain/index.d.ts +34 -8
- package/dist/types/ledger/clients/thorchain/index.d.ts.map +1 -1
- package/dist/types/ledger/clients/thorchain/protocol.d.ts +58 -0
- package/dist/types/ledger/clients/thorchain/protocol.d.ts.map +1 -0
- package/dist/types/ledger/clients/tron.d.ts +11 -8
- package/dist/types/ledger/clients/tron.d.ts.map +1 -1
- package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts +3 -2
- package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts.map +1 -1
- package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts +18 -0
- package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts.map +1 -0
- package/dist/types/ledger/clients/utxo.d.ts +10 -13
- package/dist/types/ledger/clients/utxo.d.ts.map +1 -1
- package/dist/types/ledger/clients/xrp.d.ts +4 -1
- package/dist/types/ledger/clients/xrp.d.ts.map +1 -1
- package/dist/types/ledger/clients/zcash.d.ts +53 -0
- package/dist/types/ledger/clients/zcash.d.ts.map +1 -0
- package/dist/types/ledger/helpers/dmk.d.ts +24 -0
- package/dist/types/ledger/helpers/dmk.d.ts.map +1 -0
- package/dist/types/ledger/helpers/executeDeviceAction.d.ts +34 -0
- package/dist/types/ledger/helpers/executeDeviceAction.d.ts.map +1 -0
- package/dist/types/ledger/helpers/getLedgerAddress.d.ts +1 -1
- package/dist/types/ledger/helpers/getLedgerAddress.d.ts.map +1 -1
- package/dist/types/ledger/helpers/getLedgerClient.d.ts +10 -3
- package/dist/types/ledger/helpers/getLedgerClient.d.ts.map +1 -1
- package/dist/types/ledger/helpers/index.d.ts +2 -1
- package/dist/types/ledger/helpers/index.d.ts.map +1 -1
- package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts +26 -0
- package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts.map +1 -0
- package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts +13 -0
- package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts.map +1 -0
- package/dist/types/ledger/helpers/lowS.d.ts +6 -0
- package/dist/types/ledger/helpers/lowS.d.ts.map +1 -0
- package/dist/types/ledger/helpers/rawTx.d.ts +2 -0
- package/dist/types/ledger/helpers/rawTx.d.ts.map +1 -0
- package/dist/types/ledger/index.d.ts +19 -7
- package/dist/types/ledger/index.d.ts.map +1 -1
- package/dist/types/ledger/types.d.ts +6 -10
- package/dist/types/ledger/types.d.ts.map +1 -1
- package/dist/types/trezor/index.d.ts +2 -2
- package/dist/types/trezor/index.d.ts.map +1 -1
- package/package.json +36 -36
- package/src/helpers/psbt.ts +26 -0
- package/src/ledger/clients/bitcoin.ts +575 -0
- package/src/ledger/clients/cosmos.ts +180 -51
- package/src/ledger/clients/evm.ts +238 -195
- package/src/ledger/clients/near.ts +43 -13
- package/src/ledger/clients/sui.ts +55 -68
- package/src/ledger/clients/thorchain/index.ts +210 -86
- package/src/ledger/clients/thorchain/protocol.ts +166 -0
- package/src/ledger/clients/tron.ts +49 -37
- package/src/ledger/clients/utxo-legacy-adapter.ts +35 -32
- package/src/ledger/clients/utxo-legacy-outputs.ts +122 -0
- package/src/ledger/clients/utxo.ts +108 -206
- package/src/ledger/clients/xrp.ts +39 -23
- package/src/ledger/clients/zcash.ts +440 -0
- package/src/ledger/helpers/dmk.ts +231 -0
- package/src/ledger/helpers/executeDeviceAction.ts +201 -0
- package/src/ledger/helpers/getLedgerClient.ts +101 -72
- package/src/ledger/helpers/index.ts +6 -1
- package/src/ledger/helpers/ledgerJsDmkBridge.ts +109 -0
- package/src/ledger/helpers/ledgerJsDmkTransport.ts +88 -0
- package/src/ledger/helpers/lowS.ts +30 -0
- package/src/ledger/helpers/rawTx.ts +12 -0
- package/src/ledger/index.ts +564 -369
- package/src/ledger/types.ts +15 -10
- package/src/trezor/index.ts +117 -25
- package/dist/chunk-3yr76n9s.js +0 -5
- package/dist/chunk-3yr76n9s.js.map +0 -10
- package/dist/chunk-9gy0xve4.js +0 -4
- package/dist/chunk-bwf31zxr.js +0 -4
- package/dist/chunk-bwf31zxr.js.map +0 -10
- package/dist/chunk-egtwbe6q.js +0 -4
- package/dist/chunk-egtwbe6q.js.map +0 -10
- package/dist/chunk-n05bv2n5.js +0 -5
- package/dist/chunk-vwv6z2mk.js +0 -4
- package/dist/types/ledger/clients/thorchain/common.d.ts +0 -28
- package/dist/types/ledger/clients/thorchain/common.d.ts.map +0 -1
- package/dist/types/ledger/clients/thorchain/helpers.d.ts +0 -7
- package/dist/types/ledger/clients/thorchain/helpers.d.ts.map +0 -1
- package/dist/types/ledger/clients/thorchain/lib.d.ts +0 -22
- package/dist/types/ledger/clients/thorchain/lib.d.ts.map +0 -1
- package/dist/types/ledger/clients/thorchain/utils.d.ts +0 -2
- package/dist/types/ledger/clients/thorchain/utils.d.ts.map +0 -1
- package/dist/types/ledger/clients/utxo-psbt.d.ts +0 -18
- package/dist/types/ledger/clients/utxo-psbt.d.ts.map +0 -1
- package/dist/types/ledger/cosmosTypes.d.ts +0 -40
- package/dist/types/ledger/cosmosTypes.d.ts.map +0 -1
- package/dist/types/ledger/helpers/getLedgerTransport.d.ts +0 -3
- package/dist/types/ledger/helpers/getLedgerTransport.d.ts.map +0 -1
- package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts +0 -14
- package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts.map +0 -1
- package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts +0 -14
- package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts.map +0 -1
- package/src/ledger/clients/thorchain/common.ts +0 -93
- package/src/ledger/clients/thorchain/helpers.ts +0 -120
- package/src/ledger/clients/thorchain/lib.ts +0 -258
- package/src/ledger/clients/thorchain/utils.ts +0 -69
- package/src/ledger/clients/utxo-psbt.ts +0 -172
- package/src/ledger/cosmosTypes.ts +0 -98
- package/src/ledger/helpers/getLedgerTransport.ts +0 -102
- package/src/ledger/helpers/ledgerNetworkInfo.ts +0 -127
- package/src/ledger/interfaces/CosmosLedgerInterface.ts +0 -67
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UserInteractionRequired } from "@ledgerhq/device-management-kit";
|
|
1
2
|
import type Sui from "@ledgerhq/hw-app-sui";
|
|
2
3
|
import type Transport from "@ledgerhq/hw-transport";
|
|
3
4
|
import {
|
|
@@ -8,55 +9,57 @@ import {
|
|
|
8
9
|
SwapKitError,
|
|
9
10
|
} from "@swapkit/helpers";
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
LEDGER_USER_INTERACTION_REQUIRED,
|
|
14
|
+
type LedgerJsClientParams,
|
|
15
|
+
normalizeLedgerJsClientParams,
|
|
16
|
+
runLedgerJsOperation,
|
|
17
|
+
} from "../helpers/ledgerJsDmkBridge";
|
|
18
|
+
|
|
19
|
+
type SuiLedgerParams = LedgerJsClientParams<DerivationPathArray | string>;
|
|
12
20
|
|
|
13
21
|
export class SuiLedgerInterface {
|
|
14
|
-
derivationPath: string;
|
|
15
|
-
ledgerApp: InstanceType<typeof Sui> | null = null;
|
|
22
|
+
readonly derivationPath: string;
|
|
16
23
|
address: string | null = null;
|
|
17
24
|
publicKey: Uint8Array | null = null;
|
|
18
|
-
private readonly
|
|
25
|
+
private readonly connection: Omit<SuiLedgerParams, "derivationPath">;
|
|
19
26
|
|
|
20
|
-
constructor(
|
|
27
|
+
constructor(paramsOrPath?: SuiLedgerParams | DerivationPathArray | string, transport?: Transport) {
|
|
28
|
+
const { derivationPath, ...connection } = normalizeLedgerJsClientParams({ paramsOrPath, transport });
|
|
21
29
|
this.derivationPath =
|
|
22
30
|
typeof derivationPath === "string"
|
|
23
31
|
? derivationPath
|
|
24
32
|
: derivationPathToString(derivationPath || NetworkDerivationPath[Chain.Sui]);
|
|
25
|
-
this.
|
|
33
|
+
this.connection = connection;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Standard format is: m/44'/784'/0'/0/0 (only first 3 hardened, has 'm/' prefix)
|
|
32
|
-
*/
|
|
33
|
-
private getLedgerPath(): string {
|
|
34
|
-
return this.derivationPath
|
|
35
|
-
.replace(/^m\//, "") // Remove 'm/' prefix
|
|
36
|
-
.replace(/\/(\d+)\/(\d+)$/, "/$1'/$2'"); // Make last two components hardened
|
|
36
|
+
// The Sui app wants every level hardened and no `m/`: m/44'/784'/0'/0/0 becomes 44'/784'/0'/0'/0'.
|
|
37
|
+
private getLedgerPath() {
|
|
38
|
+
return this.derivationPath.replace(/^m\//, "").replace(/\/(\d+)\/(\d+)$/, "/$1'/$2'");
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
private async
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
private async runSuiOperation<Output>({
|
|
42
|
+
operation,
|
|
43
|
+
requiredUserInteraction,
|
|
44
|
+
}: {
|
|
45
|
+
operation: (app: InstanceType<typeof Sui>) => Promise<Output>;
|
|
46
|
+
requiredUserInteraction?: UserInteractionRequired;
|
|
47
|
+
}) {
|
|
43
48
|
const SuiApp = (await import("@ledgerhq/hw-app-sui")).default;
|
|
44
|
-
|
|
49
|
+
return runLedgerJsOperation({
|
|
50
|
+
appName: "Sui",
|
|
51
|
+
connection: this.connection,
|
|
52
|
+
createApp: (ledgerTransport) => new SuiApp(ledgerTransport),
|
|
53
|
+
operation,
|
|
54
|
+
requiredUserInteraction,
|
|
55
|
+
});
|
|
45
56
|
}
|
|
46
57
|
|
|
47
|
-
async connect()
|
|
48
|
-
await this.createTransportAndLedger();
|
|
49
|
-
|
|
50
|
-
if (!this.ledgerApp) {
|
|
51
|
-
throw new SwapKitError("wallet_ledger_transport_error");
|
|
52
|
-
}
|
|
53
|
-
|
|
58
|
+
async connect() {
|
|
54
59
|
const ledgerPath = this.getLedgerPath();
|
|
55
|
-
const result = await this.
|
|
60
|
+
const result = await this.runSuiOperation({ operation: (app) => app.getPublicKey(ledgerPath) });
|
|
56
61
|
|
|
57
|
-
if (!result?.publicKey)
|
|
58
|
-
throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
59
|
-
}
|
|
62
|
+
if (!result?.publicKey) throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
60
63
|
|
|
61
64
|
this.publicKey = result.publicKey;
|
|
62
65
|
this.address = `0x${Buffer.from(result.address).toString("hex")}`;
|
|
@@ -64,71 +67,55 @@ export class SuiLedgerInterface {
|
|
|
64
67
|
return this.address;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
toSuiAddress()
|
|
68
|
-
if (!this.address)
|
|
69
|
-
throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
70
|
-
}
|
|
70
|
+
toSuiAddress() {
|
|
71
|
+
if (!this.address) throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
71
72
|
return this.address;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
async getAddress()
|
|
75
|
+
async getAddress() {
|
|
75
76
|
if (this.address) return this.address;
|
|
76
77
|
return await this.connect();
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
async signTransaction(
|
|
80
|
-
input: Uint8Array | { transaction: Uint8Array },
|
|
81
|
-
): Promise<{ bytes: string; signature: string }> {
|
|
80
|
+
async signTransaction(input: Uint8Array | { transaction: Uint8Array }) {
|
|
82
81
|
const txBytes = input instanceof Uint8Array ? input : input.transaction;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!this.ledgerApp) {
|
|
86
|
-
throw new SwapKitError("wallet_ledger_transport_error");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!this.publicKey) {
|
|
90
|
-
throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
91
|
-
}
|
|
82
|
+
if (!this.publicKey) throw new SwapKitError("wallet_ledger_failed_to_get_address");
|
|
92
83
|
|
|
93
84
|
try {
|
|
94
85
|
const ledgerPath = this.getLedgerPath();
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
// [intent_scope=0 (TransactionData), intent_version=0, app_id=0] + transaction_bytes
|
|
98
|
-
// The Ledger SUI app expects the INTENT MESSAGE, not raw transaction bytes
|
|
86
|
+
// The app signs the intent message, not the raw bytes. Its three-byte prefix (scope TransactionData,
|
|
87
|
+
// version V0, app Sui) is all zeros, which the fresh buffer already holds.
|
|
99
88
|
const intentMessage = new Uint8Array(3 + txBytes.length);
|
|
100
|
-
intentMessage[0] = 0; // IntentScope: TransactionData
|
|
101
|
-
intentMessage[1] = 0; // IntentVersion: V0
|
|
102
|
-
intentMessage[2] = 0; // AppId: Sui
|
|
103
89
|
intentMessage.set(txBytes, 3);
|
|
104
90
|
|
|
105
|
-
const result = await this.
|
|
91
|
+
const result = await this.runSuiOperation({
|
|
92
|
+
operation: (app) => app.signTransaction(ledgerPath, intentMessage),
|
|
93
|
+
requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
|
|
94
|
+
});
|
|
106
95
|
|
|
107
|
-
if (!result?.signature)
|
|
108
|
-
throw new SwapKitError("wallet_ledger_signing_error");
|
|
109
|
-
}
|
|
96
|
+
if (!result?.signature) throw new SwapKitError("wallet_ledger_signing_error");
|
|
110
97
|
|
|
111
|
-
// SUI signature format: [scheme_flag (1 byte)] + [signature (64 bytes)] + [public_key (32 bytes)]
|
|
112
|
-
// Scheme flag 0x00 = Ed25519
|
|
113
98
|
const pubKey = this.publicKey.length === 33 ? this.publicKey.slice(1) : this.publicKey;
|
|
114
99
|
if (pubKey.length !== 32) {
|
|
115
100
|
throw new SwapKitError("wallet_ledger_signing_error", { error: "Invalid public key length" });
|
|
116
101
|
}
|
|
117
102
|
|
|
103
|
+
// Sui serialised signature: Ed25519 flag (0x00), 64-byte signature, 32-byte public key.
|
|
118
104
|
const serializedSignature = new Uint8Array(1 + 64 + 32);
|
|
119
|
-
serializedSignature[0] = 0x00; // Ed25519 scheme flag
|
|
120
105
|
serializedSignature.set(result.signature, 1);
|
|
121
106
|
serializedSignature.set(pubKey, 65);
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
return {
|
|
109
|
+
bytes: Buffer.from(txBytes).toString("base64"),
|
|
110
|
+
signature: Buffer.from(serializedSignature).toString("base64"),
|
|
111
|
+
};
|
|
127
112
|
} catch (error) {
|
|
113
|
+
if (error instanceof SwapKitError) throw error;
|
|
128
114
|
throw new SwapKitError("wallet_ledger_signing_error", { error });
|
|
129
115
|
}
|
|
130
116
|
}
|
|
131
117
|
}
|
|
132
118
|
|
|
133
|
-
export
|
|
134
|
-
new SuiLedgerInterface(
|
|
119
|
+
export function SuiLedger(paramsOrPath?: SuiLedgerParams | DerivationPathArray, transport?: Transport) {
|
|
120
|
+
return new SuiLedgerInterface(paramsOrPath, transport);
|
|
121
|
+
}
|
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import type { AccountData, AminoSignResponse, StdSignDoc } from "@cosmjs/amino";
|
|
2
|
+
import type { Command, DmkError, UserInteractionRequired } from "@ledgerhq/device-management-kit";
|
|
2
3
|
import type Transport from "@ledgerhq/hw-transport";
|
|
3
|
-
import { base64 } from "@scure/base";
|
|
4
|
+
import { base64, hex } from "@scure/base";
|
|
4
5
|
import { type DerivationPathArray, NetworkDerivationPath, SwapKitError } from "@swapkit/helpers";
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import { getLedgerDMKSession, type LedgerDMKSession } from "../../helpers/dmk";
|
|
8
|
+
import {
|
|
9
|
+
executeLedgerDeviceAction,
|
|
10
|
+
LEDGER_USER_INTERACTION_REQUIRED,
|
|
11
|
+
type LedgerDeviceActionStateHandler,
|
|
12
|
+
toLedgerDeviceError,
|
|
13
|
+
ZONDAX_USER_REFUSED_STATUS_WORDS,
|
|
14
|
+
} from "../../helpers/executeDeviceAction";
|
|
15
|
+
import { toLowSSignature } from "../../helpers/lowS";
|
|
16
|
+
import {
|
|
17
|
+
getThorAddressCommand,
|
|
18
|
+
getThorLegacyVersion,
|
|
19
|
+
getThorSignCommands,
|
|
20
|
+
invalidThorAppVersionMessage,
|
|
21
|
+
parseThorAddressResponse,
|
|
22
|
+
sendThorLegacyCommand,
|
|
23
|
+
type ThorCommand,
|
|
24
|
+
} from "./protocol";
|
|
25
|
+
|
|
26
|
+
const THOR_APP_NAME = "THORChain";
|
|
27
|
+
|
|
28
|
+
interface THORChainLedgerParams {
|
|
29
|
+
derivationPath?: DerivationPathArray;
|
|
30
|
+
dmkSession?: LedgerDMKSession;
|
|
31
|
+
onDeviceActionState?: LedgerDeviceActionStateHandler;
|
|
32
|
+
transport?: Transport;
|
|
33
|
+
}
|
|
9
34
|
|
|
10
35
|
type ThorchainAssetObject = { chain?: string; symbol?: string; synth?: boolean; ticker?: string };
|
|
11
36
|
|
|
@@ -18,9 +43,7 @@ function getAminoAssetDenom(asset: string | ThorchainAssetObject) {
|
|
|
18
43
|
const symbol = (asset.symbol || asset.ticker || "").toUpperCase();
|
|
19
44
|
|
|
20
45
|
if (!(chain && symbol)) return symbol || chain || "";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return `${chain}.${symbol}`;
|
|
46
|
+
return `${chain}${asset.synth ? "/" : "."}${symbol}`;
|
|
24
47
|
}
|
|
25
48
|
|
|
26
49
|
export function normalizeThorchainLedgerSignDoc(signDoc: StdSignDoc): StdSignDoc {
|
|
@@ -43,123 +66,224 @@ export function normalizeThorchainLedgerSignDoc(signDoc: StdSignDoc): StdSignDoc
|
|
|
43
66
|
};
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
// cosmjs is loaded lazily: Bun rejects its CJS `require("@scure/base")` next to the ESM-only `@swapkit/*` graph.
|
|
70
|
+
async function getFixedSignature({ signature }: { signature: Uint8Array }) {
|
|
71
|
+
const { Secp256k1Signature } = await import("@cosmjs/crypto");
|
|
72
|
+
try {
|
|
73
|
+
return toLowSSignature(Secp256k1Signature.fromDer(signature).toFixedLength());
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new SwapKitError("wallet_ledger_invalid_signature", error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
48
78
|
|
|
49
|
-
|
|
79
|
+
async function executeThorLegacyCommands({ commands, transport }: { commands: ThorCommand[]; transport: Transport }) {
|
|
80
|
+
const version = await getThorLegacyVersion({ transport });
|
|
81
|
+
if (!version.startsWith("2.")) {
|
|
82
|
+
const { InvalidResponseFormatError } = await import("@ledgerhq/device-management-kit");
|
|
83
|
+
throw new InvalidResponseFormatError(invalidThorAppVersionMessage({ version }));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let response = new Uint8Array();
|
|
87
|
+
for (const command of commands) {
|
|
88
|
+
response = await sendThorLegacyCommand({ command, transport });
|
|
89
|
+
}
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class THORChainLedger {
|
|
94
|
+
readonly derivationPath: DerivationPathArray;
|
|
95
|
+
private readonly dmkSession?: LedgerDMKSession;
|
|
96
|
+
private readonly onDeviceActionState?: LedgerDeviceActionStateHandler;
|
|
97
|
+
private pubKey: Uint8Array | undefined;
|
|
98
|
+
private readonly transport?: Transport;
|
|
99
|
+
|
|
100
|
+
constructor({
|
|
101
|
+
derivationPath = NetworkDerivationPath.THOR,
|
|
102
|
+
dmkSession,
|
|
103
|
+
onDeviceActionState,
|
|
104
|
+
transport,
|
|
105
|
+
}: THORChainLedgerParams = {}) {
|
|
106
|
+
if (dmkSession && transport) {
|
|
107
|
+
throw new SwapKitError("wallet_ledger_invalid_params", {
|
|
108
|
+
message: "Provide either a Ledger DMK session or a LedgerJS transport, not both",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
50
111
|
|
|
51
|
-
constructor(derivationPath: DerivationPathArray = NetworkDerivationPath.THOR, transport?: Transport) {
|
|
52
|
-
super(transport);
|
|
53
|
-
this.chain = "thor";
|
|
54
112
|
this.derivationPath = derivationPath;
|
|
113
|
+
this.dmkSession = dmkSession;
|
|
114
|
+
this.onDeviceActionState = onDeviceActionState;
|
|
115
|
+
this.transport = transport;
|
|
55
116
|
}
|
|
56
117
|
|
|
57
118
|
get pubkey() {
|
|
58
|
-
return this.pubKey;
|
|
119
|
+
return this.pubKey ? base64.encode(this.pubKey) : null;
|
|
59
120
|
}
|
|
60
121
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
122
|
+
private executeCommands = async ({
|
|
123
|
+
commands,
|
|
124
|
+
requiredUserInteraction,
|
|
125
|
+
}: {
|
|
126
|
+
commands: ThorCommand[];
|
|
127
|
+
requiredUserInteraction: UserInteractionRequired;
|
|
128
|
+
}) => {
|
|
129
|
+
if (this.transport) {
|
|
130
|
+
try {
|
|
131
|
+
return await executeThorLegacyCommands({ commands, transport: this.transport });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// hw-transport rejects with status errors; map them as the DMK action below does.
|
|
134
|
+
throw toLedgerDeviceError(error, { userRefusedStatusWords: ZONDAX_USER_REFUSED_STATUS_WORDS });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
64
137
|
|
|
65
|
-
this.
|
|
138
|
+
const dmkSession = this.dmkSession ?? (await getLedgerDMKSession());
|
|
139
|
+
|
|
140
|
+
const {
|
|
141
|
+
Apdu,
|
|
142
|
+
CallTaskInAppDeviceAction,
|
|
143
|
+
CommandResultFactory,
|
|
144
|
+
DmkResultFactory,
|
|
145
|
+
InvalidResponseFormatError,
|
|
146
|
+
InvalidStatusWordError,
|
|
147
|
+
isSuccessCommandResult,
|
|
148
|
+
UnknownDeviceExchangeError,
|
|
149
|
+
} = await import("@ledgerhq/device-management-kit");
|
|
150
|
+
const toDmkCommand = (command: ThorCommand): Command<Uint8Array> => {
|
|
151
|
+
const apdu = command.getApdu();
|
|
66
152
|
|
|
67
|
-
|
|
68
|
-
|
|
153
|
+
return {
|
|
154
|
+
getApdu: () => new Apdu(apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.data),
|
|
155
|
+
name: command.name,
|
|
156
|
+
parseResponse: (response) => {
|
|
157
|
+
try {
|
|
158
|
+
return CommandResultFactory<Uint8Array>({ data: command.parseResponse(response) });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// Keep the status word so rejections and wrong-app errors map to typed SwapKit errors.
|
|
161
|
+
const errorCode = hex.encode(response.statusCode);
|
|
162
|
+
return CommandResultFactory<Uint8Array>({
|
|
163
|
+
error: Object.assign(new InvalidStatusWordError(error instanceof Error ? error.message : String(error)), {
|
|
164
|
+
errorCode,
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
};
|
|
69
171
|
|
|
70
|
-
|
|
71
|
-
|
|
172
|
+
const deviceAction = new CallTaskInAppDeviceAction<{ response: Uint8Array }, DmkError, UserInteractionRequired>({
|
|
173
|
+
input: {
|
|
174
|
+
appName: THOR_APP_NAME,
|
|
175
|
+
requiredUserInteraction,
|
|
176
|
+
skipOpenApp: false,
|
|
177
|
+
task: async (internalApi) => {
|
|
178
|
+
try {
|
|
179
|
+
const sessionState = internalApi.getDeviceSessionState();
|
|
180
|
+
const version = "currentApp" in sessionState ? sessionState.currentApp.version : undefined;
|
|
181
|
+
if (!version?.startsWith("2.")) {
|
|
182
|
+
return DmkResultFactory<{ response: Uint8Array }, DmkError>({
|
|
183
|
+
error: new InvalidResponseFormatError(invalidThorAppVersionMessage({ version })),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let response: Uint8Array<ArrayBufferLike> = new Uint8Array();
|
|
188
|
+
for (const command of commands) {
|
|
189
|
+
const result = await internalApi.sendCommand(toDmkCommand(command));
|
|
190
|
+
if (!isSuccessCommandResult(result)) {
|
|
191
|
+
return DmkResultFactory<{ response: Uint8Array }, DmkError>({ error: result.error });
|
|
192
|
+
}
|
|
193
|
+
response = result.data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return DmkResultFactory<{ response: Uint8Array }, DmkError>({ data: { response } });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return DmkResultFactory<{ response: Uint8Array }, DmkError>({
|
|
199
|
+
error: new UnknownDeviceExchangeError(error),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
72
205
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
|
|
206
|
+
const result = await executeLedgerDeviceAction({
|
|
207
|
+
action: () => dmkSession.dmk.executeDeviceAction({ deviceAction, sessionId: dmkSession.sessionId }),
|
|
208
|
+
onDeviceActionState: this.onDeviceActionState,
|
|
209
|
+
userRefusedStatusWords: ZONDAX_USER_REFUSED_STATUS_WORDS,
|
|
210
|
+
});
|
|
77
211
|
|
|
78
|
-
|
|
212
|
+
return result.response;
|
|
213
|
+
};
|
|
79
214
|
|
|
80
|
-
|
|
215
|
+
private getAddressData = async ({ checkOnDevice }: { checkOnDevice: boolean }) => {
|
|
216
|
+
const response = await this.executeCommands({
|
|
217
|
+
commands: [getThorAddressCommand({ checkOnDevice, hrp: "thor", path: this.derivationPath })],
|
|
218
|
+
requiredUserInteraction: checkOnDevice
|
|
219
|
+
? LEDGER_USER_INTERACTION_REQUIRED.VerifyAddress
|
|
220
|
+
: LEDGER_USER_INTERACTION_REQUIRED.None,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return parseThorAddressResponse({ response });
|
|
81
224
|
};
|
|
82
225
|
|
|
83
|
-
|
|
84
|
-
await this.
|
|
226
|
+
private signBytes = async ({ message }: { message: Uint8Array }) => {
|
|
227
|
+
const response = await this.executeCommands({
|
|
228
|
+
commands: getThorSignCommands({ message, path: this.derivationPath }),
|
|
229
|
+
requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
|
|
230
|
+
});
|
|
85
231
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
232
|
+
return getFixedSignature({ signature: response });
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
connect = async () => {
|
|
236
|
+
const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
|
|
237
|
+
this.pubKey = publicKey;
|
|
238
|
+
return address;
|
|
239
|
+
};
|
|
90
240
|
|
|
91
|
-
|
|
241
|
+
getAddressAndPubKey = async () => {
|
|
242
|
+
const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
|
|
243
|
+
this.pubKey = publicKey;
|
|
92
244
|
|
|
93
|
-
return
|
|
245
|
+
return { bech32_address: address, compressed_pk: publicKey, error_message: "No errors", return_code: 0x9000 };
|
|
94
246
|
};
|
|
95
247
|
|
|
96
|
-
|
|
97
|
-
await this.
|
|
248
|
+
showAddressAndPubKey = async () => {
|
|
249
|
+
const { address, publicKey } = await this.getAddressData({ checkOnDevice: true });
|
|
250
|
+
this.pubKey = publicKey;
|
|
98
251
|
|
|
99
|
-
|
|
252
|
+
return { bech32_address: address, compressed_pk: publicKey, error_message: "No errors", return_code: 0x9000 };
|
|
253
|
+
};
|
|
100
254
|
|
|
101
|
-
|
|
255
|
+
signTransaction = async ({ rawTx, sequence = "0" }: { rawTx: string; sequence?: string }) => {
|
|
256
|
+
if (!this.pubKey) await this.connect();
|
|
257
|
+
const pubKey = this.pubKey;
|
|
258
|
+
if (!pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
|
|
102
259
|
|
|
103
|
-
this.
|
|
260
|
+
const signature = await this.signBytes({ message: new TextEncoder().encode(rawTx) });
|
|
104
261
|
|
|
105
262
|
return [
|
|
106
263
|
{
|
|
107
|
-
pub_key: { type: "tendermint/PubKeySecp256k1", value:
|
|
264
|
+
pub_key: { type: "tendermint/PubKeySecp256k1", value: base64.encode(pubKey) },
|
|
108
265
|
sequence,
|
|
109
|
-
signature:
|
|
266
|
+
signature: base64.encode(signature),
|
|
110
267
|
},
|
|
111
268
|
];
|
|
112
269
|
};
|
|
113
270
|
|
|
114
271
|
signAmino = async (signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> => {
|
|
115
|
-
await this.
|
|
272
|
+
const account = (await this.getAccounts()).find(({ address }) => address === signerAddress);
|
|
273
|
+
if (!account) throw new SwapKitError("wallet_ledger_address_not_found", { address: signerAddress });
|
|
116
274
|
|
|
117
|
-
const
|
|
118
|
-
if (!account) {
|
|
119
|
-
throw new SwapKitError("wallet_ledger_address_not_found", { address: signerAddress });
|
|
120
|
-
}
|
|
275
|
+
const { encodeSecp256k1Signature, serializeSignDoc } = await import("@cosmjs/amino");
|
|
121
276
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const serializeSignDoc = importedAmino.serializeSignDoc ?? importedAmino.default?.serializeSignDoc;
|
|
126
|
-
const normalizedSignDoc = normalizeThorchainLedgerSignDoc(signDoc);
|
|
127
|
-
|
|
128
|
-
const { return_code, error_message, signature } = await this.ledgerApp.sign(
|
|
129
|
-
this.derivationPath,
|
|
130
|
-
serializeSignDoc(normalizedSignDoc),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
this.validateResponse(return_code, error_message);
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
signature: encodeSecp256k1Signature(account.pubkey, base64.decode(getSignature(signature))),
|
|
137
|
-
// CosmJS rebuilds bodyBytes from the returned `signed` doc. Keep the
|
|
138
|
-
// original direct-shaped doc there so MsgDeposit protobuf assets do not
|
|
139
|
-
// get round-tripped through the incomplete Amino converter.
|
|
140
|
-
signed: signDoc,
|
|
141
|
-
};
|
|
277
|
+
const signature = await this.signBytes({ message: serializeSignDoc(normalizeThorchainLedgerSignDoc(signDoc)) });
|
|
278
|
+
|
|
279
|
+
return { signature: encodeSecp256k1Signature(account.pubkey, signature), signed: signDoc };
|
|
142
280
|
};
|
|
143
281
|
|
|
144
282
|
getAccounts = async (): Promise<readonly AccountData[]> => {
|
|
145
|
-
await this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this.pubKey = base64.encode(compressed_pk);
|
|
150
|
-
|
|
151
|
-
return [{ address: bech32_address, algo: "secp256k1", pubkey: compressed_pk }];
|
|
283
|
+
const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
|
|
284
|
+
this.pubKey = publicKey;
|
|
285
|
+
return [{ address, algo: "secp256k1", pubkey: publicKey }];
|
|
152
286
|
};
|
|
153
287
|
|
|
154
|
-
sign = async (message: string) => {
|
|
155
|
-
await this.checkOrCreateTransportAndLedger(true);
|
|
156
|
-
|
|
157
|
-
const { return_code, error_message, signature } = await this.ledgerApp.sign(this.derivationPath, message);
|
|
158
|
-
|
|
159
|
-
if (!this.pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
|
|
160
|
-
|
|
161
|
-
this.validateResponse(return_code, error_message);
|
|
162
|
-
|
|
163
|
-
return getSignature(signature);
|
|
164
|
-
};
|
|
288
|
+
sign = async (message: string) => base64.encode(await this.signBytes({ message: new TextEncoder().encode(message) }));
|
|
165
289
|
}
|