@mysten/ledger-signer 0.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.
@@ -0,0 +1,73 @@
1
+ import { getInputObjects } from "./objects.mjs";
2
+ import { SignatureWithBytes, Signer } from "@mysten/sui/cryptography";
3
+ import { Ed25519PublicKey } from "@mysten/sui/keypairs/ed25519";
4
+ import SuiLedgerClient, { Resolution } from "@mysten/ledgerjs-hw-app-sui";
5
+ import { ClientWithCoreApi } from "@mysten/sui/client";
6
+
7
+ //#region src/index.d.ts
8
+ /**
9
+ * Configuration options for initializing the LedgerSigner.
10
+ */
11
+ interface LedgerSignerOptions {
12
+ publicKey: Ed25519PublicKey;
13
+ derivationPath: string;
14
+ ledgerClient: SuiLedgerClient;
15
+ suiClient: ClientWithCoreApi;
16
+ }
17
+ /**
18
+ * Ledger integrates with the Sui blockchain to provide signing capabilities using Ledger devices.
19
+ */
20
+ declare class LedgerSigner extends Signer {
21
+ #private;
22
+ /**
23
+ * Creates an instance of LedgerSigner. It's expected to call the static `fromDerivationPath` method to create an instance.
24
+ * @example
25
+ * ```
26
+ * const signer = await LedgerSigner.fromDerivationPath(derivationPath, options);
27
+ * ```
28
+ */
29
+ constructor({
30
+ publicKey,
31
+ derivationPath,
32
+ ledgerClient,
33
+ suiClient
34
+ }: LedgerSignerOptions);
35
+ /**
36
+ * Retrieves the key scheme used by this signer.
37
+ */
38
+ getKeyScheme(): "ED25519";
39
+ /**
40
+ * Retrieves the public key associated with this signer.
41
+ * @returns The Ed25519PublicKey instance.
42
+ */
43
+ getPublicKey(): Ed25519PublicKey;
44
+ /**
45
+ * Signs the provided transaction bytes.
46
+ * @returns The signed transaction bytes and signature.
47
+ */
48
+ signTransaction(bytes: Uint8Array, bcsObjects?: Uint8Array[], resolution?: Resolution): Promise<SignatureWithBytes>;
49
+ /**
50
+ * Signs the provided personal message.
51
+ * @returns The signed message bytes and signature.
52
+ */
53
+ signPersonalMessage(bytes: Uint8Array): Promise<SignatureWithBytes>;
54
+ /**
55
+ * Prepares the signer by fetching and setting the public key from a Ledger device.
56
+ * It is recommended to initialize an `LedgerSigner` instance using this function.
57
+ * @returns A promise that resolves once a `LedgerSigner` instance is prepared (public key is set).
58
+ */
59
+ static fromDerivationPath(derivationPath: string, ledgerClient: SuiLedgerClient, suiClient: ClientWithCoreApi): Promise<LedgerSigner>;
60
+ /**
61
+ * Generic signing is not supported by Ledger.
62
+ * @throws Always throws an error indicating generic signing is unsupported.
63
+ */
64
+ sign(): never;
65
+ /**
66
+ * Generic signing is not supported by Ledger.
67
+ * @throws Always throws an error indicating generic signing is unsupported.
68
+ */
69
+ signWithIntent(): never;
70
+ }
71
+ //#endregion
72
+ export { LedgerSigner, LedgerSignerOptions, getInputObjects };
73
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;AAoBA;;;AAAA,UAAiB,mBAAA;EAChB,SAAA,EAAW,gBAAA;EACX,cAAA;EACA,YAAA,EAAc,eAAA;EACd,SAAA,EAAW,iBAAA;AAAA;;;;cAMC,YAAA,SAAqB,MAAA;EAAA;EANtB;;;AAMZ;;;;;IAae,SAAA;IAAW,cAAA;IAAgB,YAAA;IAAc;EAAA,GAAa,mBAAA;EAA3B;;;EAWhC,YAAA,CAAA;EAiBD;;;;EATC,YAAA,CAAA,GAAY,gBAAA;EA0CqB;;;;EAlC3B,eAAA,CACd,KAAA,EAAO,UAAA,EACP,UAAA,GAAa,UAAA,IACb,UAAA,GAAa,UAAA,GACX,OAAA,CAAQ,kBAAA;EA0DkB;;;;EA5Bd,mBAAA,CAAoB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,kBAAA;EA1E9B;;;;;EAAA,OAmGpB,kBAAA,CACZ,cAAA,UACA,YAAA,EAAc,eAAA,EACd,SAAA,EAAW,iBAAA,GAAiB,OAAA,CAAA,YAAA;EAzFJ;;;;EA4GhB,IAAA,CAAA;EA5G2D;;;;EAoH3D,cAAA,CAAA;AAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { getInputObjects } from "./objects.mjs";
2
+ import { Signer, messageWithIntent, toSerializedSignature } from "@mysten/sui/cryptography";
3
+ import { Ed25519PublicKey } from "@mysten/sui/keypairs/ed25519";
4
+ import { Transaction } from "@mysten/sui/transactions";
5
+ import { toBase64 } from "@mysten/sui/utils";
6
+ import { bcs } from "@mysten/sui/bcs";
7
+
8
+ //#region src/index.ts
9
+ /**
10
+ * Ledger integrates with the Sui blockchain to provide signing capabilities using Ledger devices.
11
+ */
12
+ var LedgerSigner = class LedgerSigner extends Signer {
13
+ #derivationPath;
14
+ #publicKey;
15
+ #ledgerClient;
16
+ #suiClient;
17
+ /**
18
+ * Creates an instance of LedgerSigner. It's expected to call the static `fromDerivationPath` method to create an instance.
19
+ * @example
20
+ * ```
21
+ * const signer = await LedgerSigner.fromDerivationPath(derivationPath, options);
22
+ * ```
23
+ */
24
+ constructor({ publicKey, derivationPath, ledgerClient, suiClient }) {
25
+ super();
26
+ this.#publicKey = publicKey;
27
+ this.#derivationPath = derivationPath;
28
+ this.#ledgerClient = ledgerClient;
29
+ this.#suiClient = suiClient;
30
+ }
31
+ /**
32
+ * Retrieves the key scheme used by this signer.
33
+ */
34
+ getKeyScheme() {
35
+ return "ED25519";
36
+ }
37
+ /**
38
+ * Retrieves the public key associated with this signer.
39
+ * @returns The Ed25519PublicKey instance.
40
+ */
41
+ getPublicKey() {
42
+ return this.#publicKey;
43
+ }
44
+ /**
45
+ * Signs the provided transaction bytes.
46
+ * @returns The signed transaction bytes and signature.
47
+ */
48
+ async signTransaction(bytes, bcsObjects, resolution) {
49
+ const transactionOptions = bcsObjects ? { bcsObjects } : await getInputObjects(Transaction.from(bytes), this.#suiClient).catch(() => ({ bcsObjects: [] }));
50
+ const intentMessage = messageWithIntent("TransactionData", bytes);
51
+ const { signature } = await this.#ledgerClient.signTransaction(this.#derivationPath, intentMessage, transactionOptions, resolution);
52
+ return {
53
+ bytes: toBase64(bytes),
54
+ signature: toSerializedSignature({
55
+ signature,
56
+ signatureScheme: this.getKeyScheme(),
57
+ publicKey: this.#publicKey
58
+ })
59
+ };
60
+ }
61
+ /**
62
+ * Signs the provided personal message.
63
+ * @returns The signed message bytes and signature.
64
+ */
65
+ async signPersonalMessage(bytes) {
66
+ const intentMessage = messageWithIntent("PersonalMessage", bcs.byteVector().serialize(bytes).toBytes());
67
+ const { signature } = await this.#ledgerClient.signTransaction(this.#derivationPath, intentMessage);
68
+ return {
69
+ bytes: toBase64(bytes),
70
+ signature: toSerializedSignature({
71
+ signature,
72
+ signatureScheme: this.getKeyScheme(),
73
+ publicKey: this.#publicKey
74
+ })
75
+ };
76
+ }
77
+ /**
78
+ * Prepares the signer by fetching and setting the public key from a Ledger device.
79
+ * It is recommended to initialize an `LedgerSigner` instance using this function.
80
+ * @returns A promise that resolves once a `LedgerSigner` instance is prepared (public key is set).
81
+ */
82
+ static async fromDerivationPath(derivationPath, ledgerClient, suiClient) {
83
+ const { publicKey } = await ledgerClient.getPublicKey(derivationPath);
84
+ if (!publicKey) throw new Error("Failed to get public key from Ledger.");
85
+ return new LedgerSigner({
86
+ derivationPath,
87
+ publicKey: new Ed25519PublicKey(publicKey),
88
+ ledgerClient,
89
+ suiClient
90
+ });
91
+ }
92
+ /**
93
+ * Generic signing is not supported by Ledger.
94
+ * @throws Always throws an error indicating generic signing is unsupported.
95
+ */
96
+ sign() {
97
+ throw new Error("Ledger Signer does not support generic signing.");
98
+ }
99
+ /**
100
+ * Generic signing is not supported by Ledger.
101
+ * @throws Always throws an error indicating generic signing is unsupported.
102
+ */
103
+ signWithIntent() {
104
+ throw new Error("Ledger Signer does not support generic signing.");
105
+ }
106
+ };
107
+
108
+ //#endregion
109
+ export { LedgerSigner, getInputObjects };
110
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["#publicKey","#derivationPath","#ledgerClient","#suiClient"],"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type SuiLedgerClient from '@mysten/ledgerjs-hw-app-sui';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { SignatureWithBytes } from '@mysten/sui/cryptography';\nimport { messageWithIntent, Signer, toSerializedSignature } from '@mysten/sui/cryptography';\nimport { Ed25519PublicKey } from '@mysten/sui/keypairs/ed25519';\nimport { Transaction } from '@mysten/sui/transactions';\nimport { toBase64 } from '@mysten/sui/utils';\n\nimport { bcs } from '@mysten/sui/bcs';\nimport { getInputObjects } from './objects.js';\nimport type { Resolution } from '@mysten/ledgerjs-hw-app-sui';\n\nexport { getInputObjects } from './objects.js';\n\n/**\n * Configuration options for initializing the LedgerSigner.\n */\nexport interface LedgerSignerOptions {\n\tpublicKey: Ed25519PublicKey;\n\tderivationPath: string;\n\tledgerClient: SuiLedgerClient;\n\tsuiClient: ClientWithCoreApi;\n}\n\n/**\n * Ledger integrates with the Sui blockchain to provide signing capabilities using Ledger devices.\n */\nexport class LedgerSigner extends Signer {\n\t#derivationPath: string;\n\t#publicKey: Ed25519PublicKey;\n\t#ledgerClient: SuiLedgerClient;\n\t#suiClient: ClientWithCoreApi;\n\n\t/**\n\t * Creates an instance of LedgerSigner. It's expected to call the static `fromDerivationPath` method to create an instance.\n\t * @example\n\t * ```\n\t * const signer = await LedgerSigner.fromDerivationPath(derivationPath, options);\n\t * ```\n\t */\n\tconstructor({ publicKey, derivationPath, ledgerClient, suiClient }: LedgerSignerOptions) {\n\t\tsuper();\n\t\tthis.#publicKey = publicKey;\n\t\tthis.#derivationPath = derivationPath;\n\t\tthis.#ledgerClient = ledgerClient;\n\t\tthis.#suiClient = suiClient;\n\t}\n\n\t/**\n\t * Retrieves the key scheme used by this signer.\n\t */\n\toverride getKeyScheme() {\n\t\treturn 'ED25519' as const;\n\t}\n\n\t/**\n\t * Retrieves the public key associated with this signer.\n\t * @returns The Ed25519PublicKey instance.\n\t */\n\toverride getPublicKey() {\n\t\treturn this.#publicKey;\n\t}\n\n\t/**\n\t * Signs the provided transaction bytes.\n\t * @returns The signed transaction bytes and signature.\n\t */\n\toverride async signTransaction(\n\t\tbytes: Uint8Array,\n\t\tbcsObjects?: Uint8Array[],\n\t\tresolution?: Resolution,\n\t): Promise<SignatureWithBytes> {\n\t\tconst transactionOptions = bcsObjects\n\t\t\t? { bcsObjects }\n\t\t\t: await getInputObjects(Transaction.from(bytes), this.#suiClient).catch(() => ({\n\t\t\t\t\t// Fail gracefully so network errors or serialization issues don't break transaction signing:\n\t\t\t\t\tbcsObjects: [],\n\t\t\t\t}));\n\n\t\tconst intentMessage = messageWithIntent('TransactionData', bytes);\n\t\tconst { signature } = await this.#ledgerClient.signTransaction(\n\t\t\tthis.#derivationPath,\n\t\t\tintentMessage,\n\t\t\ttransactionOptions,\n\t\t\tresolution,\n\t\t);\n\n\t\treturn {\n\t\t\tbytes: toBase64(bytes),\n\t\t\tsignature: toSerializedSignature({\n\t\t\t\tsignature,\n\t\t\t\tsignatureScheme: this.getKeyScheme(),\n\t\t\t\tpublicKey: this.#publicKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\t/**\n\t * Signs the provided personal message.\n\t * @returns The signed message bytes and signature.\n\t */\n\toverride async signPersonalMessage(bytes: Uint8Array): Promise<SignatureWithBytes> {\n\t\tconst intentMessage = messageWithIntent(\n\t\t\t'PersonalMessage',\n\t\t\tbcs.byteVector().serialize(bytes).toBytes(),\n\t\t);\n\t\tconst { signature } = await this.#ledgerClient.signTransaction(\n\t\t\tthis.#derivationPath,\n\t\t\tintentMessage,\n\t\t);\n\n\t\treturn {\n\t\t\tbytes: toBase64(bytes),\n\t\t\tsignature: toSerializedSignature({\n\t\t\t\tsignature,\n\t\t\t\tsignatureScheme: this.getKeyScheme(),\n\t\t\t\tpublicKey: this.#publicKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\t/**\n\t * Prepares the signer by fetching and setting the public key from a Ledger device.\n\t * It is recommended to initialize an `LedgerSigner` instance using this function.\n\t * @returns A promise that resolves once a `LedgerSigner` instance is prepared (public key is set).\n\t */\n\tstatic async fromDerivationPath(\n\t\tderivationPath: string,\n\t\tledgerClient: SuiLedgerClient,\n\t\tsuiClient: ClientWithCoreApi,\n\t) {\n\t\tconst { publicKey } = await ledgerClient.getPublicKey(derivationPath);\n\t\tif (!publicKey) {\n\t\t\tthrow new Error('Failed to get public key from Ledger.');\n\t\t}\n\n\t\treturn new LedgerSigner({\n\t\t\tderivationPath,\n\t\t\tpublicKey: new Ed25519PublicKey(publicKey),\n\t\t\tledgerClient,\n\t\t\tsuiClient,\n\t\t});\n\t}\n\n\t/**\n\t * Generic signing is not supported by Ledger.\n\t * @throws Always throws an error indicating generic signing is unsupported.\n\t */\n\toverride sign(): never {\n\t\tthrow new Error('Ledger Signer does not support generic signing.');\n\t}\n\n\t/**\n\t * Generic signing is not supported by Ledger.\n\t * @throws Always throws an error indicating generic signing is unsupported.\n\t */\n\toverride signWithIntent(): never {\n\t\tthrow new Error('Ledger Signer does not support generic signing.');\n\t}\n}\n"],"mappings":";;;;;;;;;;;AA8BA,IAAa,eAAb,MAAa,qBAAqB,OAAO;CACxC;CACA;CACA;CACA;;;;;;;;CASA,YAAY,EAAE,WAAW,gBAAgB,cAAc,aAAkC;AACxF,SAAO;AACP,QAAKA,YAAa;AAClB,QAAKC,iBAAkB;AACvB,QAAKC,eAAgB;AACrB,QAAKC,YAAa;;;;;CAMnB,AAAS,eAAe;AACvB,SAAO;;;;;;CAOR,AAAS,eAAe;AACvB,SAAO,MAAKH;;;;;;CAOb,MAAe,gBACd,OACA,YACA,YAC8B;EAC9B,MAAM,qBAAqB,aACxB,EAAE,YAAY,GACd,MAAM,gBAAgB,YAAY,KAAK,MAAM,EAAE,MAAKG,UAAW,CAAC,aAAa,EAE7E,YAAY,EAAE,EACd,EAAE;EAEL,MAAM,gBAAgB,kBAAkB,mBAAmB,MAAM;EACjE,MAAM,EAAE,cAAc,MAAM,MAAKD,aAAc,gBAC9C,MAAKD,gBACL,eACA,oBACA,WACA;AAED,SAAO;GACN,OAAO,SAAS,MAAM;GACtB,WAAW,sBAAsB;IAChC;IACA,iBAAiB,KAAK,cAAc;IACpC,WAAW,MAAKD;IAChB,CAAC;GACF;;;;;;CAOF,MAAe,oBAAoB,OAAgD;EAClF,MAAM,gBAAgB,kBACrB,mBACA,IAAI,YAAY,CAAC,UAAU,MAAM,CAAC,SAAS,CAC3C;EACD,MAAM,EAAE,cAAc,MAAM,MAAKE,aAAc,gBAC9C,MAAKD,gBACL,cACA;AAED,SAAO;GACN,OAAO,SAAS,MAAM;GACtB,WAAW,sBAAsB;IAChC;IACA,iBAAiB,KAAK,cAAc;IACpC,WAAW,MAAKD;IAChB,CAAC;GACF;;;;;;;CAQF,aAAa,mBACZ,gBACA,cACA,WACC;EACD,MAAM,EAAE,cAAc,MAAM,aAAa,aAAa,eAAe;AACrE,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,wCAAwC;AAGzD,SAAO,IAAI,aAAa;GACvB;GACA,WAAW,IAAI,iBAAiB,UAAU;GAC1C;GACA;GACA,CAAC;;;;;;CAOH,AAAS,OAAc;AACtB,QAAM,IAAI,MAAM,kDAAkD;;;;;;CAOnE,AAAS,iBAAwB;AAChC,QAAM,IAAI,MAAM,kDAAkD"}
@@ -0,0 +1,10 @@
1
+ import { Transaction } from "@mysten/sui/transactions";
2
+ import { ClientWithCoreApi } from "@mysten/sui/client";
3
+
4
+ //#region src/objects.d.ts
5
+ declare const getInputObjects: (transaction: Transaction, client: ClientWithCoreApi) => Promise<{
6
+ bcsObjects: Uint8Array<ArrayBuffer>[];
7
+ }>;
8
+ //#endregion
9
+ export { getInputObjects };
10
+ //# sourceMappingURL=objects.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objects.d.mts","names":[],"sources":["../src/objects.ts"],"mappings":";;;;cAMa,eAAA,GAAyB,WAAA,EAAa,WAAA,EAAa,MAAA,EAAQ,iBAAA,KAAiB,OAAA"}
@@ -0,0 +1,16 @@
1
+ //#region src/objects.ts
2
+ const getInputObjects = async (transaction, client) => {
3
+ const data = transaction.getData();
4
+ const gasObjectIds = data.gasData.payment?.map((object) => object.objectId) ?? [];
5
+ const inputObjectIds = data.inputs.map((input) => {
6
+ return input.$kind === "Object" && input.Object.$kind === "ImmOrOwnedObject" ? input.Object.ImmOrOwnedObject.objectId : null;
7
+ }).filter((objectId) => !!objectId);
8
+ return { bcsObjects: (await client.core.getObjects({
9
+ objectIds: [...gasObjectIds, ...inputObjectIds],
10
+ include: { objectBcs: true }
11
+ })).objects.filter((obj) => !(obj instanceof Error)).map((object) => object.objectBcs).filter((bytes) => !!bytes) };
12
+ };
13
+
14
+ //#endregion
15
+ export { getInputObjects };
16
+ //# sourceMappingURL=objects.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objects.mjs","names":[],"sources":["../src/objects.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transaction } from '@mysten/sui/transactions';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\n\nexport const getInputObjects = async (transaction: Transaction, client: ClientWithCoreApi) => {\n\tconst data = transaction.getData();\n\n\tconst gasObjectIds = data.gasData.payment?.map((object) => object.objectId) ?? [];\n\tconst inputObjectIds = data.inputs\n\t\t.map((input) => {\n\t\t\treturn input.$kind === 'Object' && input.Object.$kind === 'ImmOrOwnedObject'\n\t\t\t\t? input.Object.ImmOrOwnedObject.objectId\n\t\t\t\t: null;\n\t\t})\n\t\t.filter((objectId): objectId is string => !!objectId);\n\n\tconst response = await client.core.getObjects({\n\t\tobjectIds: [...gasObjectIds, ...inputObjectIds],\n\t\tinclude: {\n\t\t\tobjectBcs: true,\n\t\t},\n\t});\n\n\tconst bcsObjects = response.objects\n\t\t.filter((obj): obj is Exclude<typeof obj, Error> => !(obj instanceof Error))\n\t\t.map((object) => object.objectBcs)\n\t\t.filter((bytes): bytes is Uint8Array<ArrayBuffer> => !!bytes);\n\n\treturn { bcsObjects };\n};\n"],"mappings":";AAMA,MAAa,kBAAkB,OAAO,aAA0B,WAA8B;CAC7F,MAAM,OAAO,YAAY,SAAS;CAElC,MAAM,eAAe,KAAK,QAAQ,SAAS,KAAK,WAAW,OAAO,SAAS,IAAI,EAAE;CACjF,MAAM,iBAAiB,KAAK,OAC1B,KAAK,UAAU;AACf,SAAO,MAAM,UAAU,YAAY,MAAM,OAAO,UAAU,qBACvD,MAAM,OAAO,iBAAiB,WAC9B;GACF,CACD,QAAQ,aAAiC,CAAC,CAAC,SAAS;AActD,QAAO,EAAE,aAZQ,MAAM,OAAO,KAAK,WAAW;EAC7C,WAAW,CAAC,GAAG,cAAc,GAAG,eAAe;EAC/C,SAAS,EACR,WAAW,MACX;EACD,CAAC,EAE0B,QAC1B,QAAQ,QAA2C,EAAE,eAAe,OAAO,CAC3E,KAAK,WAAW,OAAO,UAAU,CACjC,QAAQ,UAA4C,CAAC,CAAC,MAAM,EAEzC"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@mysten/ledger-signer",
3
+ "author": "Mysten Labs <build@mystenlabs.com>",
4
+ "description": "Sui signer for Ledger hardware wallets",
5
+ "homepage": "https://sdk.mystenlabs.com",
6
+ "version": "0.0.0",
7
+ "license": "Apache-2.0",
8
+ "type": "module",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.mts",
13
+ "import": "./dist/index.mjs",
14
+ "default": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "CHANGELOG.md",
19
+ "LICENSE",
20
+ "README.md",
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/MystenLabs/ts-sdks.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/mystenlabs/ts-sdks/issues"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "engines": {
35
+ "node": ">=22"
36
+ },
37
+ "dependencies": {
38
+ "@mysten/ledgerjs-hw-app-sui": "^0.8.1"
39
+ },
40
+ "peerDependencies": {
41
+ "@mysten/sui": "^2.16.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^25.0.8",
45
+ "typescript": "^5.9.3",
46
+ "vitest": "^4.0.17",
47
+ "@mysten/sui": "^2.16.0"
48
+ },
49
+ "scripts": {
50
+ "clean": "rm -rf tsconfig.tsbuildinfo ./dist",
51
+ "build": "rm -rf dist && tsc --noEmit && tsdown",
52
+ "prettier:check": "prettier -c --ignore-unknown .",
53
+ "prettier:fix": "prettier -w --ignore-unknown .",
54
+ "oxlint:check": "oxlint .",
55
+ "oxlint:fix": "oxlint --fix",
56
+ "lint": "pnpm run oxlint:check && pnpm run prettier:check",
57
+ "lint:fix": "pnpm run oxlint:fix && pnpm run prettier:fix",
58
+ "test": "vitest run --passWithNoTests"
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,163 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type SuiLedgerClient from '@mysten/ledgerjs-hw-app-sui';
5
+ import type { ClientWithCoreApi } from '@mysten/sui/client';
6
+ import type { SignatureWithBytes } from '@mysten/sui/cryptography';
7
+ import { messageWithIntent, Signer, toSerializedSignature } from '@mysten/sui/cryptography';
8
+ import { Ed25519PublicKey } from '@mysten/sui/keypairs/ed25519';
9
+ import { Transaction } from '@mysten/sui/transactions';
10
+ import { toBase64 } from '@mysten/sui/utils';
11
+
12
+ import { bcs } from '@mysten/sui/bcs';
13
+ import { getInputObjects } from './objects.js';
14
+ import type { Resolution } from '@mysten/ledgerjs-hw-app-sui';
15
+
16
+ export { getInputObjects } from './objects.js';
17
+
18
+ /**
19
+ * Configuration options for initializing the LedgerSigner.
20
+ */
21
+ export interface LedgerSignerOptions {
22
+ publicKey: Ed25519PublicKey;
23
+ derivationPath: string;
24
+ ledgerClient: SuiLedgerClient;
25
+ suiClient: ClientWithCoreApi;
26
+ }
27
+
28
+ /**
29
+ * Ledger integrates with the Sui blockchain to provide signing capabilities using Ledger devices.
30
+ */
31
+ export class LedgerSigner extends Signer {
32
+ #derivationPath: string;
33
+ #publicKey: Ed25519PublicKey;
34
+ #ledgerClient: SuiLedgerClient;
35
+ #suiClient: ClientWithCoreApi;
36
+
37
+ /**
38
+ * Creates an instance of LedgerSigner. It's expected to call the static `fromDerivationPath` method to create an instance.
39
+ * @example
40
+ * ```
41
+ * const signer = await LedgerSigner.fromDerivationPath(derivationPath, options);
42
+ * ```
43
+ */
44
+ constructor({ publicKey, derivationPath, ledgerClient, suiClient }: LedgerSignerOptions) {
45
+ super();
46
+ this.#publicKey = publicKey;
47
+ this.#derivationPath = derivationPath;
48
+ this.#ledgerClient = ledgerClient;
49
+ this.#suiClient = suiClient;
50
+ }
51
+
52
+ /**
53
+ * Retrieves the key scheme used by this signer.
54
+ */
55
+ override getKeyScheme() {
56
+ return 'ED25519' as const;
57
+ }
58
+
59
+ /**
60
+ * Retrieves the public key associated with this signer.
61
+ * @returns The Ed25519PublicKey instance.
62
+ */
63
+ override getPublicKey() {
64
+ return this.#publicKey;
65
+ }
66
+
67
+ /**
68
+ * Signs the provided transaction bytes.
69
+ * @returns The signed transaction bytes and signature.
70
+ */
71
+ override async signTransaction(
72
+ bytes: Uint8Array,
73
+ bcsObjects?: Uint8Array[],
74
+ resolution?: Resolution,
75
+ ): Promise<SignatureWithBytes> {
76
+ const transactionOptions = bcsObjects
77
+ ? { bcsObjects }
78
+ : await getInputObjects(Transaction.from(bytes), this.#suiClient).catch(() => ({
79
+ // Fail gracefully so network errors or serialization issues don't break transaction signing:
80
+ bcsObjects: [],
81
+ }));
82
+
83
+ const intentMessage = messageWithIntent('TransactionData', bytes);
84
+ const { signature } = await this.#ledgerClient.signTransaction(
85
+ this.#derivationPath,
86
+ intentMessage,
87
+ transactionOptions,
88
+ resolution,
89
+ );
90
+
91
+ return {
92
+ bytes: toBase64(bytes),
93
+ signature: toSerializedSignature({
94
+ signature,
95
+ signatureScheme: this.getKeyScheme(),
96
+ publicKey: this.#publicKey,
97
+ }),
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Signs the provided personal message.
103
+ * @returns The signed message bytes and signature.
104
+ */
105
+ override async signPersonalMessage(bytes: Uint8Array): Promise<SignatureWithBytes> {
106
+ const intentMessage = messageWithIntent(
107
+ 'PersonalMessage',
108
+ bcs.byteVector().serialize(bytes).toBytes(),
109
+ );
110
+ const { signature } = await this.#ledgerClient.signTransaction(
111
+ this.#derivationPath,
112
+ intentMessage,
113
+ );
114
+
115
+ return {
116
+ bytes: toBase64(bytes),
117
+ signature: toSerializedSignature({
118
+ signature,
119
+ signatureScheme: this.getKeyScheme(),
120
+ publicKey: this.#publicKey,
121
+ }),
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Prepares the signer by fetching and setting the public key from a Ledger device.
127
+ * It is recommended to initialize an `LedgerSigner` instance using this function.
128
+ * @returns A promise that resolves once a `LedgerSigner` instance is prepared (public key is set).
129
+ */
130
+ static async fromDerivationPath(
131
+ derivationPath: string,
132
+ ledgerClient: SuiLedgerClient,
133
+ suiClient: ClientWithCoreApi,
134
+ ) {
135
+ const { publicKey } = await ledgerClient.getPublicKey(derivationPath);
136
+ if (!publicKey) {
137
+ throw new Error('Failed to get public key from Ledger.');
138
+ }
139
+
140
+ return new LedgerSigner({
141
+ derivationPath,
142
+ publicKey: new Ed25519PublicKey(publicKey),
143
+ ledgerClient,
144
+ suiClient,
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Generic signing is not supported by Ledger.
150
+ * @throws Always throws an error indicating generic signing is unsupported.
151
+ */
152
+ override sign(): never {
153
+ throw new Error('Ledger Signer does not support generic signing.');
154
+ }
155
+
156
+ /**
157
+ * Generic signing is not supported by Ledger.
158
+ * @throws Always throws an error indicating generic signing is unsupported.
159
+ */
160
+ override signWithIntent(): never {
161
+ throw new Error('Ledger Signer does not support generic signing.');
162
+ }
163
+ }
package/src/objects.ts ADDED
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { Transaction } from '@mysten/sui/transactions';
5
+ import type { ClientWithCoreApi } from '@mysten/sui/client';
6
+
7
+ export const getInputObjects = async (transaction: Transaction, client: ClientWithCoreApi) => {
8
+ const data = transaction.getData();
9
+
10
+ const gasObjectIds = data.gasData.payment?.map((object) => object.objectId) ?? [];
11
+ const inputObjectIds = data.inputs
12
+ .map((input) => {
13
+ return input.$kind === 'Object' && input.Object.$kind === 'ImmOrOwnedObject'
14
+ ? input.Object.ImmOrOwnedObject.objectId
15
+ : null;
16
+ })
17
+ .filter((objectId): objectId is string => !!objectId);
18
+
19
+ const response = await client.core.getObjects({
20
+ objectIds: [...gasObjectIds, ...inputObjectIds],
21
+ include: {
22
+ objectBcs: true,
23
+ },
24
+ });
25
+
26
+ const bcsObjects = response.objects
27
+ .filter((obj): obj is Exclude<typeof obj, Error> => !(obj instanceof Error))
28
+ .map((object) => object.objectBcs)
29
+ .filter((bytes): bytes is Uint8Array<ArrayBuffer> => !!bytes);
30
+
31
+ return { bcsObjects };
32
+ };