Skip to content

Commit 6019986

Browse files
✨ (signer-polkadot) [LIVE-30994]: Scaffold signer kit for Polkadot (Substrate chains) (#1501)
2 parents 171f4bd + 22cd048 commit 6019986

50 files changed

Lines changed: 1298 additions & 16 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/yummy-news-bow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ledgerhq/device-signer-kit-polkadot": minor
3+
---
4+
5+
Init polkadot device management kit signer

agent-files/scripts/release/config.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const ALIASES = {
1313
"signer-cosmos": "@ledgerhq/device-signer-kit-cosmos",
1414
"signer-hyperliquid": "@ledgerhq/device-signer-kit-hyperliquid",
1515
"signer-concordium": "@ledgerhq/device-signer-kit-concordium",
16+
"signer-polkadot": "@ledgerhq/device-signer-kit-polkadot",
1617
"signer-zcash": "@ledgerhq/device-signer-kit-zcash",
1718
"signer-utils": "@ledgerhq/signer-utils",
1819
"speculos-controller": "@ledgerhq/speculos-device-controller",
@@ -48,6 +49,7 @@ const DISPLAY_NAMES = {
4849
"@ledgerhq/device-signer-kit-cosmos": "Signer Cosmos",
4950
"@ledgerhq/device-signer-kit-hyperliquid": "Signer Hyperliquid",
5051
"@ledgerhq/device-signer-kit-concordium": "Signer Concordium",
52+
"@ledgerhq/device-signer-kit-polkadot": "Signer Polkadot",
5153
"@ledgerhq/device-signer-kit-zcash": "Signer Zcash",
5254
"@ledgerhq/signer-utils": "Signer Utils",
5355
"@ledgerhq/speculos-device-controller": "Speculos Device Controller",

agent-files/skills/release/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Use these short names in the `--packages` flag. The user may use either aliases
3232
| `signer-cosmos` | `@ledgerhq/device-signer-kit-cosmos` |
3333
| `signer-hyperliquid` | `@ledgerhq/device-signer-kit-hyperliquid` |
3434
| `signer-concordium` | `@ledgerhq/device-signer-kit-concordium` |
35+
| `signer-polkadot` | `@ledgerhq/device-signer-kit-polkadot` |
3536
| `signer-zcash` | `@ledgerhq/device-signer-kit-zcash` |
3637
| `signer-utils` | `@ledgerhq/signer-utils` |
3738
| `context-module` | `@ledgerhq/context-module` |

apps/docs/pages/docs/references/signers/_meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export default {
77
aleo: "Signer Aleo",
88
concordium: "Signer Concordium",
99
zcash: "Signer Zcash",
10+
polkadot: "Signer Polkadot",
1011
};
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# Polkadot Signer Kit
2+
3+
This module provides the implementation of the Ledger polkadot signer of the Device Management Kit. It enables interaction with the polkadot application on a Ledger device including:
4+
5+
- Retrieving the polkadot address using a given derivation path
6+
- Signing a polkadot transaction
7+
8+
## 🔹 Index
9+
10+
1. [How it works](#-how-it-works)
11+
2. [Installation](#-installation)
12+
3. [Initialisation](#-initialisation)
13+
4. [Use Cases](#-use-cases)
14+
- [Get Address](#use-case-1-get-address)
15+
- [Sign Transaction](#use-case-2-sign-transaction)
16+
5. [Observable Behavior](#-observable-behavior)
17+
6. [Example](#-example)
18+
19+
## 🔹 How it works
20+
21+
The Ledger Polkadot Signer utilizes the advanced capabilities of the Ledger device to provide secure operations for end users. It takes advantage of the interface provided by the Device Management Kit to establish communication with the Ledger device and execute various operations. The communication with the Ledger device is performed using [APDU](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit)s (Application Protocol Data Units), which are encapsulated within the `Command` object. These commands are then organized into tasks, allowing for the execution of complex operations with one or more APDUs. The tasks are further encapsulated within `DeviceAction` objects to handle different real-world scenarios. Finally, the Signer exposes dedicated and independent use cases that can be directly utilized by end users.
22+
23+
## 🔹 Installation
24+
25+
> **Note:** This module is not standalone; it depends on the [@ledgerhq/device-management-kit](https://github.com/LedgerHQ/device-sdk-ts/tree/develop/packages/device-management-kit) package, so you need to install it first.
26+
27+
To install the `device-signer-kit-polkadot` package, run the following command:
28+
29+
```sh
30+
npm install @ledgerhq/device-signer-kit-polkadot
31+
```
32+
33+
## 🔹 Initialisation
34+
35+
To initialise a Polkadot signer instance, you need a Ledger Device Management Kit instance and the ID of the session of the connected device. Use the `SignerPolkadotBuilder`:
36+
37+
```typescript
38+
const signerPolkadot = new SignerPolkadotBuilder({ dmk, sessionId }).build();
39+
```
40+
41+
## 🔹 Use Cases
42+
43+
The `SignerPolkadotBuilder.build()` method will return a `SignerPolkadot` instance that exposes 2 dedicated methods, each of which calls an independent use case. Each use case will return an object that contains an observable and a method called `cancel`.
44+
45+
---
46+
47+
### Use Case 1: Get Address
48+
49+
This method allows users to retrieve the polkadot address based on a given `derivationPath`.
50+
51+
```typescript
52+
const { observable, cancel } = signerPolkadot.getAddress(
53+
derivationPath,
54+
ss58Prefix,
55+
options,
56+
);
57+
```
58+
59+
#### **Parameters**
60+
61+
- `derivationPath`
62+
63+
- **Required**
64+
- **Type:** `string` (e.g., `"m/44'/0'/0'/0/0"`)
65+
- The derivation path used for the polkadot address. See [here](https://www.ledger.com/blog/understanding-crypto-addresses-and-derivation-paths) for more information.
66+
67+
- `ss58Prefix`
68+
69+
- **Required**
70+
- **Type:** `number` (e.g., `0` for Polkadot, `42` for Bittensor)
71+
- The SS58 address format prefix for the target network.
72+
73+
- `options`
74+
75+
- Optional
76+
- Type: `AddressOptions`
77+
78+
```typescript
79+
type AddressOptions = {
80+
checkOnDevice?: boolean;
81+
skipOpenApp?: boolean;
82+
};
83+
```
84+
85+
- `checkOnDevice`: An optional boolean indicating whether user confirmation on the device is required (`true`) or not (`false`).
86+
- `skipOpenApp`: An optional boolean indicating whether to skip opening the polkadot app automatically (`true`) or not (`false`).
87+
88+
#### **Returns**
89+
90+
- `observable` Emits DeviceActionState updates, including the following details:
91+
92+
```typescript
93+
type GetAddressCommandResponse = {
94+
publicKey: Uint8Array;
95+
chainCode?: Uint8Array;
96+
};
97+
```
98+
99+
- `cancel` A function to cancel the action on the Ledger device.
100+
101+
---
102+
103+
### Use Case 2: Sign Transaction
104+
105+
This method allows users to sign a polkadot transaction.
106+
107+
```typescript
108+
const { observable, cancel } = signerPolkadot.signTransaction(
109+
derivationPath,
110+
blob,
111+
metadata,
112+
options,
113+
);
114+
```
115+
116+
#### **Parameters**
117+
118+
- `derivationPath`
119+
120+
- **Required**
121+
- **Type:** `string` (e.g., `"m/44'/0'/0'/0/0"`)
122+
- The derivation path used for the polkadot transaction. See [here](https://www.ledger.com/blog/understanding-crypto-addresses-and-derivation-paths) for more information.
123+
124+
- `blob`
125+
126+
- **Required**
127+
- **Type:** `Uint8Array`
128+
- The SCALE-encoded transaction payload to be signed.
129+
130+
- `metadata`
131+
132+
- **Required**
133+
- **Type:** `Uint8Array`
134+
- The metadata proof used for transaction clear signing (Ledger Polkadot generic app).
135+
136+
- `options`
137+
138+
- Optional
139+
- Type: `TransactionOptions`
140+
141+
```typescript
142+
type TransactionOptions = {
143+
skipOpenApp?: boolean;
144+
};
145+
```
146+
147+
- `skipOpenApp`: An optional boolean indicating whether to skip opening the polkadot app automatically (`true`) or not (`false`).
148+
149+
#### **Returns**
150+
151+
- `observable` Emits DeviceActionState updates, including the following details:
152+
153+
```typescript
154+
type Signature = Uint8Array;
155+
```
156+
157+
- `cancel` A function to cancel the action on the Ledger device.
158+
159+
---
160+
161+
## 🔹 Observable Behavior
162+
163+
Each method returns an [Observable](https://rxjs.dev/guide/observable) emitting updates structured as [`DeviceActionState`](https://github.com/LedgerHQ/device-sdk-ts/blob/develop/packages/device-management-kit/src/api/device-action/model/DeviceActionState.ts). These updates reflect the operation's progress and status:
164+
165+
- **NotStarted**: The operation hasn't started.
166+
- **Pending**: The operation is in progress and may require user interaction.
167+
- **Stopped**: The operation was canceled or stopped.
168+
- **Completed**: The operation completed successfully, with results available.
169+
- **Error**: An error occurred.
170+
171+
**Example Observable Subscription:**
172+
173+
```typescript
174+
observable.subscribe({
175+
next: (state: DeviceActionState) => {
176+
switch (state.status) {
177+
case DeviceActionStatus.NotStarted: {
178+
console.log("The action is not started yet.");
179+
break;
180+
}
181+
case DeviceActionStatus.Pending: {
182+
const {
183+
intermediateValue: { requiredUserInteraction },
184+
} = state;
185+
// Access the intermediate value here, explained below
186+
console.log(
187+
"The action is pending and the intermediate value is: ",
188+
intermediateValue,
189+
);
190+
break;
191+
}
192+
case DeviceActionStatus.Stopped: {
193+
console.log("The action has been stopped.");
194+
break;
195+
}
196+
case DeviceActionStatus.Completed: {
197+
const { output } = state;
198+
// Access the output of the completed action here
199+
console.log("The action has been completed: ", output);
200+
break;
201+
}
202+
case DeviceActionStatus.Error: {
203+
const { error } = state;
204+
// Access the error here if occurred
205+
console.log("An error occurred during the action: ", error);
206+
break;
207+
}
208+
}
209+
},
210+
});
211+
```
212+
213+
**Intermediate Values in Pending Status:**
214+
215+
When the status is DeviceActionStatus.Pending, the state will include an `intermediateValue` object that provides useful information for interaction:
216+
217+
```typescript
218+
const { requiredUserInteraction } = intermediateValue;
219+
220+
switch (requiredUserInteraction) {
221+
case UserInteractionRequired.VerifyAddress: {
222+
// User needs to verify the address displayed on the device
223+
console.log("User needs to verify the address displayed on the device.");
224+
break;
225+
}
226+
case UserInteractionRequired.SignTransaction: {
227+
// User needs to sign the transaction displayed on the device
228+
console.log("User needs to sign the transaction displayed on the device.");
229+
break;
230+
}
231+
case UserInteractionRequired.SignPersonalMessage: {
232+
// User needs to sign the message displayed on the device
233+
console.log("User needs to sign the message displayed on the device.");
234+
break;
235+
}
236+
case UserInteractionRequired.None: {
237+
// No user action required
238+
console.log("No user action needed.");
239+
break;
240+
}
241+
case UserInteractionRequired.UnlockDevice: {
242+
// User needs to unlock the device
243+
console.log("The user needs to unlock the device.");
244+
break;
245+
}
246+
case UserInteractionRequired.ConfirmOpenApp: {
247+
// User needs to confirm on the device to open the app
248+
console.log("The user needs to confirm on the device to open the app.");
249+
break;
250+
}
251+
default:
252+
// Type guard to ensure all cases are handled
253+
const uncaughtUserInteraction: never = requiredUserInteraction;
254+
console.error("Unhandled user interaction case:", uncaughtUserInteraction);
255+
}
256+
```
257+
258+
## 🔹 Example
259+
260+
We encourage you to explore the Polkadot Signer by trying it out in our online [sample application](https://app.devicesdk.ledger-test.com/). Experience how it works and see its capabilities in action. Of course, you will need a Ledger device connected.

apps/sample/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@ledgerhq/device-signer-kit-cosmos": "workspace:^",
3131
"@ledgerhq/device-signer-kit-ethereum": "workspace:^",
3232
"@ledgerhq/device-signer-kit-hyperliquid": "workspace:^",
33+
"@ledgerhq/device-signer-kit-polkadot": "workspace:^",
3334
"@ledgerhq/device-signer-kit-solana": "workspace:^",
3435
"@ledgerhq/device-signer-kit-zcash": "workspace:^",
3536
"@ledgerhq/device-transport-kit-mockserver": "workspace:^",
@@ -41,10 +42,10 @@
4142
"@ledgerhq/react-ui": "catalog:",
4243
"@ledgerhq/solana-tools": "workspace:^",
4344
"@noble/secp256k1": "^2.3.0",
44-
"bs58": "catalog:",
4545
"@playwright/test": "catalog:",
4646
"@reduxjs/toolkit": "catalog:",
4747
"@sentry/nextjs": "catalog:",
48+
"bs58": "catalog:",
4849
"ethers": "catalog:",
4950
"lottie-react": "catalog:",
5051
"next": "catalog:",

apps/sample/src/app/client-layout.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { SettingsGate } from "@/providers/SettingsGate";
2626
import { SignerAleoProvider } from "@/providers/SignerAleoProvider";
2727
import { SignerCosmosProvider } from "@/providers/SignerCosmosProvider";
2828
import { SignerEthProvider } from "@/providers/SignerEthProvider";
29+
import { SignerPolkadotProvider } from "@/providers/SignerPolkadotProvider";
2930
import { SignerZcashProvider } from "@/providers/SignerZcashProvider";
3031
import { store } from "@/state/store";
3132
import { GlobalStyle } from "@/styles/globalstyles";
@@ -76,21 +77,23 @@ const ClientRootLayout: React.FC<PropsWithChildren> = ({ children }) => {
7677
<DmkProvider>
7778
<LedgerKeyringProtocolProvider>
7879
<SignerEthProvider>
79-
<SignerZcashProvider>
80-
<SignerAleoProvider>
81-
<SignerCosmosProvider>
82-
<CalInterceptorProvider>
83-
<GlobalStyle />
84-
<head>
85-
<link rel="shortcut icon" href="../favicon.png" />
86-
</head>
87-
<body>
88-
<RootApp>{children}</RootApp>
89-
</body>
90-
</CalInterceptorProvider>
91-
</SignerCosmosProvider>
92-
</SignerAleoProvider>
93-
</SignerZcashProvider>
80+
<SignerPolkadotProvider>
81+
<SignerZcashProvider>
82+
<SignerAleoProvider>
83+
<SignerCosmosProvider>
84+
<CalInterceptorProvider>
85+
<GlobalStyle />
86+
<head>
87+
<link rel="shortcut icon" href="../favicon.png" />
88+
</head>
89+
<body>
90+
<RootApp>{children}</RootApp>
91+
</body>
92+
</CalInterceptorProvider>
93+
</SignerCosmosProvider>
94+
</SignerAleoProvider>
95+
</SignerZcashProvider>
96+
</SignerPolkadotProvider>
9497
</SignerEthProvider>
9598
</LedgerKeyringProtocolProvider>
9699
</DmkProvider>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use client";
2+
import React from "react";
3+
4+
import { SessionIdWrapper } from "@/components/SessionIdWrapper";
5+
import { SignerPolkadotView } from "@/components/SignerPolkadotView";
6+
7+
const Signer: React.FC = () => {
8+
return <SessionIdWrapper ChildComponent={SignerPolkadotView} />;
9+
};
10+
11+
export default Signer;

0 commit comments

Comments
 (0)