@rango-dev/provider-ledger 0.0.0-experimental-936229e8-20251208

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,94 @@
1
+ import type { SolanaWeb3Signer } from '@rango-dev/signer-solana';
2
+ import type { Transaction, VersionedTransaction } from '@solana/web3.js';
3
+ import type { GenericSigner, SolanaTransaction } from 'rango-types';
4
+
5
+ import { generalSolanaTransactionExecutor } from '@rango-dev/signer-solana';
6
+ import { dynamicImportWithRefinedError } from '@rango-dev/wallets-shared';
7
+ import { PublicKey } from '@solana/web3.js';
8
+ import { SignerError, SignerErrorCode } from 'rango-types';
9
+
10
+ import { getDerivationPath } from '../state.js';
11
+ import {
12
+ getLedgerError,
13
+ transportConnect,
14
+ transportDisconnect,
15
+ } from '../utils.js';
16
+
17
+ export function isVersionedTransaction(
18
+ transaction: Transaction | VersionedTransaction
19
+ ): transaction is VersionedTransaction {
20
+ return 'version' in transaction;
21
+ }
22
+
23
+ export class SolanaSigner implements GenericSigner<SolanaTransaction> {
24
+ async signMessage(msg: string): Promise<string> {
25
+ try {
26
+ const transport = await transportConnect();
27
+ const LedgerAppSolana = (
28
+ await dynamicImportWithRefinedError(
29
+ async () => await import('@ledgerhq/hw-app-solana')
30
+ )
31
+ ).default;
32
+ const solana = new LedgerAppSolana(transport);
33
+
34
+ const result = await solana.signOffchainMessage(
35
+ getDerivationPath(),
36
+ Buffer.from(msg)
37
+ );
38
+ return result.signature.toString();
39
+ } catch (error) {
40
+ throw new SignerError(SignerErrorCode.SIGN_TX_ERROR, undefined, error);
41
+ }
42
+ }
43
+
44
+ async signAndSendTx(tx: SolanaTransaction): Promise<{ hash: string }> {
45
+ try {
46
+ const DefaultSolanaSigner: SolanaWeb3Signer = async (
47
+ solanaWeb3Transaction: Transaction | VersionedTransaction
48
+ ) => {
49
+ const transport = await transportConnect();
50
+ const LedgerAppSolana = (
51
+ await dynamicImportWithRefinedError(
52
+ async () => await import('@ledgerhq/hw-app-solana')
53
+ )
54
+ ).default;
55
+ const solana = new LedgerAppSolana(transport);
56
+
57
+ let signResult;
58
+ if (isVersionedTransaction(solanaWeb3Transaction)) {
59
+ signResult = await solana.signTransaction(
60
+ getDerivationPath(),
61
+ solanaWeb3Transaction.message.serialize() as Buffer
62
+ );
63
+ } else {
64
+ signResult = await solana.signTransaction(
65
+ getDerivationPath(),
66
+ solanaWeb3Transaction.serialize()
67
+ );
68
+ }
69
+
70
+ const addressResult = await solana.getAddress(getDerivationPath());
71
+
72
+ const publicKey = new PublicKey(addressResult.address);
73
+
74
+ solanaWeb3Transaction.addSignature(
75
+ publicKey,
76
+ Buffer.from(signResult.signature)
77
+ );
78
+
79
+ const serializedTx = solanaWeb3Transaction.serialize();
80
+
81
+ return serializedTx;
82
+ };
83
+ const hash = await generalSolanaTransactionExecutor(
84
+ tx,
85
+ DefaultSolanaSigner
86
+ );
87
+ return { hash };
88
+ } catch (error) {
89
+ throw getLedgerError(error);
90
+ } finally {
91
+ await transportDisconnect();
92
+ }
93
+ }
94
+ }
package/src/state.ts ADDED
@@ -0,0 +1,10 @@
1
+ // We keep derivationPath here because we need to maintain it for signing transactions after it is set in connect method
2
+ let derivationPath = '';
3
+
4
+ export function setDerivationPath(path: string) {
5
+ derivationPath = path;
6
+ }
7
+
8
+ export function getDerivationPath() {
9
+ return derivationPath;
10
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,140 @@
1
+ import type Transport from '@ledgerhq/hw-transport';
2
+
3
+ import { getAltStatusMessage } from '@ledgerhq/errors';
4
+ import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
5
+ import { CAIP_SOLANA_CHAIN_ID } from '@rango-dev/wallets-core/namespaces/solana';
6
+ import {
7
+ dynamicImportWithRefinedError,
8
+ ETHEREUM_CHAIN_ID,
9
+ type ProviderConnectResult,
10
+ } from '@rango-dev/wallets-shared';
11
+ import bs58 from 'bs58';
12
+
13
+ import { HEXADECIMAL_BASE } from './constants.js';
14
+ import { getDerivationPath } from './state.js';
15
+
16
+ export type Provider = Map<string, unknown>;
17
+
18
+ export function ledger(): Provider | null {
19
+ /*
20
+ * Instances have a required property which is `chainId` and is using in swap execution.
21
+ * Here we are setting it as Ethereum always since we are supporting only eth for now.
22
+ */
23
+ const instances = new Map();
24
+
25
+ instances.set(LegacyNetworks.ETHEREUM, { chainId: ETHEREUM_CHAIN_ID });
26
+ instances.set(LegacyNetworks.SOLANA, { chainId: LegacyNetworks.SOLANA });
27
+
28
+ return instances;
29
+ }
30
+
31
+ const ledgerFrequentErrorMessages: { [statusCode: number]: string } = {
32
+ 0x5515: 'The device is locked',
33
+ 0x650f: 'Related application is not ready on your device',
34
+ 0x6985: 'Action denied by user',
35
+ };
36
+
37
+ function getLedgerErrorMessage(statusCode: number): string {
38
+ if (ledgerFrequentErrorMessages[statusCode]) {
39
+ return ledgerFrequentErrorMessages[statusCode];
40
+ } else if (getAltStatusMessage(statusCode)) {
41
+ return getAltStatusMessage(statusCode) as string;
42
+ }
43
+
44
+ return `Ledger device unknown error 0x${statusCode.toString(
45
+ HEXADECIMAL_BASE
46
+ )}`; // Hexadecimal numbers are more commonly recognized and utilized for representing ledger error codes
47
+ }
48
+
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ export function getLedgerError(error: any) {
51
+ if (error?.statusCode) {
52
+ return new Error(getLedgerErrorMessage(error.statusCode));
53
+ }
54
+
55
+ if (error?.code === 'INSUFFICIENT_FUNDS') {
56
+ return new Error('Insufficient funds for transaction');
57
+ }
58
+ return error;
59
+ }
60
+
61
+ export function standardizeAndThrowLedgerError(_: unknown, error: unknown) {
62
+ throw getLedgerError(error);
63
+ }
64
+
65
+ export async function getEthereumAccounts(): Promise<ProviderConnectResult> {
66
+ try {
67
+ const transport = await transportConnect();
68
+ const LedgerAppEth = (
69
+ await dynamicImportWithRefinedError(
70
+ async () => await import('@ledgerhq/hw-app-eth')
71
+ )
72
+ ).default;
73
+ const eth = new LedgerAppEth(transport);
74
+ const derivationPath = getDerivationPath();
75
+
76
+ const accounts: string[] = [];
77
+
78
+ const result = await eth.getAddress(derivationPath, false, true);
79
+ accounts.push(result.address);
80
+
81
+ return {
82
+ accounts: accounts,
83
+ chainId: ETHEREUM_CHAIN_ID,
84
+ derivationPath,
85
+ };
86
+ } catch (error: unknown) {
87
+ throw getLedgerError(error);
88
+ } finally {
89
+ await transportDisconnect();
90
+ }
91
+ }
92
+
93
+ export async function getSolanaAccounts(): Promise<ProviderConnectResult> {
94
+ try {
95
+ const transport = await transportConnect();
96
+ const LedgerAppSolana = (
97
+ await dynamicImportWithRefinedError(
98
+ async () => await import('@ledgerhq/hw-app-solana')
99
+ )
100
+ ).default;
101
+ const solana = new LedgerAppSolana(transport);
102
+ const derivationPath = getDerivationPath();
103
+
104
+ const accounts: string[] = [];
105
+
106
+ const result = await solana.getAddress(derivationPath);
107
+ accounts.push(bs58.encode(result.address));
108
+
109
+ return {
110
+ accounts: accounts,
111
+ chainId: CAIP_SOLANA_CHAIN_ID,
112
+ derivationPath,
113
+ };
114
+ } catch (error: unknown) {
115
+ throw getLedgerError(error);
116
+ } finally {
117
+ await transportDisconnect();
118
+ }
119
+ }
120
+
121
+ let transportConnection: Transport | null = null;
122
+
123
+ export async function transportConnect() {
124
+ const TransportWebHID = (
125
+ await dynamicImportWithRefinedError(
126
+ async () => await import('@ledgerhq/hw-transport-webhid')
127
+ )
128
+ ).default;
129
+
130
+ transportConnection = await TransportWebHID.create();
131
+
132
+ return transportConnection;
133
+ }
134
+
135
+ export async function transportDisconnect() {
136
+ if (transportConnection) {
137
+ await transportConnection.close();
138
+ transportConnection = null;
139
+ }
140
+ }