@sip-protocol/sdk 0.1.0 → 0.1.4
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/index.d.mts +3236 -1554
- package/dist/index.d.ts +3236 -1554
- package/dist/index.js +9185 -3521
- package/dist/index.mjs +8995 -3376
- package/package.json +5 -2
- package/src/adapters/near-intents.ts +48 -35
- package/src/adapters/oneclick-client.ts +9 -1
- package/src/compliance/compliance-manager.ts +1035 -0
- package/src/compliance/index.ts +43 -0
- package/src/index.ts +129 -2
- package/src/payment/index.ts +54 -0
- package/src/payment/payment.ts +623 -0
- package/src/payment/stablecoins.ts +306 -0
- package/src/privacy.ts +127 -94
- package/src/proofs/circuits/fulfillment_proof.json +1 -0
- package/src/proofs/circuits/funding_proof.json +1 -0
- package/src/proofs/circuits/validity_proof.json +1 -0
- package/src/proofs/interface.ts +13 -1
- package/src/proofs/noir.ts +967 -97
- package/src/secure-memory.ts +147 -0
- package/src/sip.ts +399 -37
- package/src/stealth.ts +116 -84
- package/src/treasury/index.ts +43 -0
- package/src/treasury/treasury.ts +911 -0
- package/src/wallet/hardware/index.ts +87 -0
- package/src/wallet/hardware/ledger.ts +628 -0
- package/src/wallet/hardware/mock.ts +667 -0
- package/src/wallet/hardware/trezor.ts +657 -0
- package/src/wallet/hardware/types.ts +317 -0
- package/src/wallet/index.ts +40 -0
- package/src/zcash/shielded-service.ts +59 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardware Wallet Module
|
|
3
|
+
*
|
|
4
|
+
* Support for hardware wallets (Ledger, Trezor) in SIP Protocol.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import {
|
|
9
|
+
* LedgerWalletAdapter,
|
|
10
|
+
* TrezorWalletAdapter,
|
|
11
|
+
* MockLedgerAdapter,
|
|
12
|
+
* } from '@sip-protocol/sdk'
|
|
13
|
+
*
|
|
14
|
+
* // Connect to Ledger
|
|
15
|
+
* const ledger = new LedgerWalletAdapter({
|
|
16
|
+
* chain: 'ethereum',
|
|
17
|
+
* accountIndex: 0,
|
|
18
|
+
* })
|
|
19
|
+
* await ledger.connect()
|
|
20
|
+
*
|
|
21
|
+
* // Connect to Trezor
|
|
22
|
+
* const trezor = new TrezorWalletAdapter({
|
|
23
|
+
* chain: 'ethereum',
|
|
24
|
+
* manifestEmail: 'dev@myapp.com',
|
|
25
|
+
* manifestAppName: 'My DApp',
|
|
26
|
+
* manifestUrl: 'https://myapp.com',
|
|
27
|
+
* })
|
|
28
|
+
* await trezor.connect()
|
|
29
|
+
*
|
|
30
|
+
* // Use mock adapter for testing
|
|
31
|
+
* const mock = new MockLedgerAdapter({
|
|
32
|
+
* chain: 'ethereum',
|
|
33
|
+
* })
|
|
34
|
+
* await mock.connect()
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Hardware wallet adapters require external dependencies:
|
|
39
|
+
* - Ledger: `@ledgerhq/hw-transport-webusb`, `@ledgerhq/hw-app-eth`
|
|
40
|
+
* - Trezor: `@trezor/connect-web`
|
|
41
|
+
*
|
|
42
|
+
* Mock adapters are included for testing without hardware.
|
|
43
|
+
*
|
|
44
|
+
* @module hardware
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// Types
|
|
48
|
+
export {
|
|
49
|
+
type HardwareWalletType,
|
|
50
|
+
type LedgerModel,
|
|
51
|
+
type TrezorModel,
|
|
52
|
+
type HardwareConnectionStatus,
|
|
53
|
+
type TransportType,
|
|
54
|
+
type HardwareDeviceInfo,
|
|
55
|
+
type HardwareWalletConfig,
|
|
56
|
+
type LedgerConfig,
|
|
57
|
+
type TrezorConfig,
|
|
58
|
+
type HardwareSignRequest,
|
|
59
|
+
type HardwareEthereumTx,
|
|
60
|
+
type HardwareSignature,
|
|
61
|
+
type HardwareAccount,
|
|
62
|
+
type HardwareTransport,
|
|
63
|
+
type HardwareErrorCodeType,
|
|
64
|
+
HardwareErrorCode,
|
|
65
|
+
HardwareWalletError,
|
|
66
|
+
DerivationPath,
|
|
67
|
+
getDerivationPath,
|
|
68
|
+
supportsWebUSB,
|
|
69
|
+
supportsWebHID,
|
|
70
|
+
supportsWebBluetooth,
|
|
71
|
+
getAvailableTransports,
|
|
72
|
+
} from './types'
|
|
73
|
+
|
|
74
|
+
// Ledger adapter
|
|
75
|
+
export { LedgerWalletAdapter, createLedgerAdapter } from './ledger'
|
|
76
|
+
|
|
77
|
+
// Trezor adapter
|
|
78
|
+
export { TrezorWalletAdapter, createTrezorAdapter } from './trezor'
|
|
79
|
+
|
|
80
|
+
// Mock adapters for testing
|
|
81
|
+
export {
|
|
82
|
+
type MockHardwareConfig,
|
|
83
|
+
MockLedgerAdapter,
|
|
84
|
+
MockTrezorAdapter,
|
|
85
|
+
createMockLedgerAdapter,
|
|
86
|
+
createMockTrezorAdapter,
|
|
87
|
+
} from './mock'
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ledger Hardware Wallet Adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides integration with Ledger hardware wallets (Nano S, Nano X, etc.)
|
|
5
|
+
* for secure transaction signing.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { LedgerWalletAdapter } from '@sip-protocol/sdk'
|
|
10
|
+
*
|
|
11
|
+
* const ledger = new LedgerWalletAdapter({
|
|
12
|
+
* chain: 'ethereum',
|
|
13
|
+
* accountIndex: 0,
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* await ledger.connect()
|
|
17
|
+
* const signature = await ledger.signMessage(message)
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Requires Ledger device with appropriate app installed:
|
|
22
|
+
* - Ethereum: Ethereum app
|
|
23
|
+
* - Solana: Solana app
|
|
24
|
+
*
|
|
25
|
+
* External dependencies (install separately):
|
|
26
|
+
* - @ledgerhq/hw-transport-webusb
|
|
27
|
+
* - @ledgerhq/hw-app-eth (for Ethereum)
|
|
28
|
+
* - @ledgerhq/hw-app-solana (for Solana)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
ChainId,
|
|
33
|
+
HexString,
|
|
34
|
+
Asset,
|
|
35
|
+
Signature,
|
|
36
|
+
UnsignedTransaction,
|
|
37
|
+
SignedTransaction,
|
|
38
|
+
TransactionReceipt,
|
|
39
|
+
} from '@sip-protocol/types'
|
|
40
|
+
import { WalletErrorCode } from '@sip-protocol/types'
|
|
41
|
+
import { BaseWalletAdapter } from '../base-adapter'
|
|
42
|
+
import { WalletError } from '../errors'
|
|
43
|
+
import {
|
|
44
|
+
type LedgerConfig,
|
|
45
|
+
type HardwareDeviceInfo,
|
|
46
|
+
type HardwareAccount,
|
|
47
|
+
type HardwareSignature,
|
|
48
|
+
type HardwareEthereumTx,
|
|
49
|
+
type HardwareTransport,
|
|
50
|
+
HardwareErrorCode,
|
|
51
|
+
HardwareWalletError,
|
|
52
|
+
getDerivationPath,
|
|
53
|
+
supportsWebUSB,
|
|
54
|
+
} from './types'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ledger wallet adapter
|
|
58
|
+
*
|
|
59
|
+
* Supports Ethereum and Solana chains via Ledger device apps.
|
|
60
|
+
*/
|
|
61
|
+
export class LedgerWalletAdapter extends BaseWalletAdapter {
|
|
62
|
+
readonly chain: ChainId
|
|
63
|
+
readonly name: string = 'ledger'
|
|
64
|
+
|
|
65
|
+
private config: LedgerConfig
|
|
66
|
+
private transport: HardwareTransport | null = null
|
|
67
|
+
private app: LedgerApp | null = null
|
|
68
|
+
private _derivationPath: string
|
|
69
|
+
private _deviceInfo: HardwareDeviceInfo | null = null
|
|
70
|
+
private _account: HardwareAccount | null = null
|
|
71
|
+
|
|
72
|
+
constructor(config: LedgerConfig) {
|
|
73
|
+
super()
|
|
74
|
+
this.chain = config.chain
|
|
75
|
+
this.config = {
|
|
76
|
+
accountIndex: 0,
|
|
77
|
+
timeout: 30000,
|
|
78
|
+
...config,
|
|
79
|
+
}
|
|
80
|
+
this._derivationPath = config.derivationPath ??
|
|
81
|
+
getDerivationPath(config.chain, config.accountIndex ?? 0)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get device information
|
|
86
|
+
*/
|
|
87
|
+
get deviceInfo(): HardwareDeviceInfo | null {
|
|
88
|
+
return this._deviceInfo
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get current derivation path
|
|
93
|
+
*/
|
|
94
|
+
get derivationPath(): string {
|
|
95
|
+
return this._derivationPath
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get current account
|
|
100
|
+
*/
|
|
101
|
+
get account(): HardwareAccount | null {
|
|
102
|
+
return this._account
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Connect to Ledger device
|
|
107
|
+
*/
|
|
108
|
+
async connect(): Promise<void> {
|
|
109
|
+
if (!supportsWebUSB()) {
|
|
110
|
+
throw new HardwareWalletError(
|
|
111
|
+
'WebUSB not supported in this browser',
|
|
112
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
113
|
+
'ledger'
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this._connectionState = 'connecting'
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Dynamic import of Ledger transport
|
|
121
|
+
// Users must install @ledgerhq/hw-transport-webusb
|
|
122
|
+
const TransportWebUSB = await this.loadTransport()
|
|
123
|
+
|
|
124
|
+
// Open transport
|
|
125
|
+
this.transport = await TransportWebUSB.create() as unknown as HardwareTransport
|
|
126
|
+
|
|
127
|
+
// Load appropriate app
|
|
128
|
+
this.app = await this.loadApp()
|
|
129
|
+
|
|
130
|
+
// Get account from device
|
|
131
|
+
this._account = await this.getAccountFromDevice()
|
|
132
|
+
|
|
133
|
+
this._deviceInfo = {
|
|
134
|
+
manufacturer: 'ledger',
|
|
135
|
+
model: 'unknown', // Would be detected from transport
|
|
136
|
+
isLocked: false,
|
|
137
|
+
currentApp: this.getAppName(),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this._connectionState = 'error'
|
|
143
|
+
throw this.handleError(error)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Disconnect from Ledger device
|
|
149
|
+
*/
|
|
150
|
+
async disconnect(): Promise<void> {
|
|
151
|
+
if (this.transport) {
|
|
152
|
+
await this.transport.close()
|
|
153
|
+
this.transport = null
|
|
154
|
+
}
|
|
155
|
+
this.app = null
|
|
156
|
+
this._account = null
|
|
157
|
+
this._deviceInfo = null
|
|
158
|
+
this.setDisconnected()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sign a message
|
|
163
|
+
*/
|
|
164
|
+
async signMessage(message: Uint8Array): Promise<Signature> {
|
|
165
|
+
this.requireConnected()
|
|
166
|
+
|
|
167
|
+
if (!this.app) {
|
|
168
|
+
throw new HardwareWalletError(
|
|
169
|
+
'Ledger app not loaded',
|
|
170
|
+
HardwareErrorCode.APP_NOT_OPEN,
|
|
171
|
+
'ledger'
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const sig = await this.signMessageOnDevice(message)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
signature: sig.signature,
|
|
180
|
+
publicKey: this._account!.publicKey,
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw this.handleError(error)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Sign a transaction
|
|
189
|
+
*/
|
|
190
|
+
async signTransaction(tx: UnsignedTransaction): Promise<SignedTransaction> {
|
|
191
|
+
this.requireConnected()
|
|
192
|
+
|
|
193
|
+
if (!this.app) {
|
|
194
|
+
throw new HardwareWalletError(
|
|
195
|
+
'Ledger app not loaded',
|
|
196
|
+
HardwareErrorCode.APP_NOT_OPEN,
|
|
197
|
+
'ledger'
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const sig = await this.signTransactionOnDevice(tx)
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
unsigned: tx,
|
|
206
|
+
signatures: [
|
|
207
|
+
{
|
|
208
|
+
signature: sig.signature,
|
|
209
|
+
publicKey: this._account!.publicKey,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
serialized: sig.signature, // Actual implementation would serialize properly
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw this.handleError(error)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Sign and send transaction
|
|
221
|
+
*
|
|
222
|
+
* Note: Hardware wallets can only sign, not send. This returns a signed
|
|
223
|
+
* transaction that must be broadcast separately.
|
|
224
|
+
*/
|
|
225
|
+
async signAndSendTransaction(tx: UnsignedTransaction): Promise<TransactionReceipt> {
|
|
226
|
+
// Hardware wallets can't send transactions directly
|
|
227
|
+
// Sign and return as if pending broadcast
|
|
228
|
+
const signed = await this.signTransaction(tx)
|
|
229
|
+
|
|
230
|
+
// Return mock receipt - actual sending requires RPC/network
|
|
231
|
+
return {
|
|
232
|
+
txHash: signed.serialized.slice(0, 66) as HexString,
|
|
233
|
+
status: 'pending',
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get native token balance
|
|
239
|
+
*
|
|
240
|
+
* Note: Hardware wallets don't track balances - this requires RPC.
|
|
241
|
+
*/
|
|
242
|
+
async getBalance(): Promise<bigint> {
|
|
243
|
+
throw new WalletError(
|
|
244
|
+
'Hardware wallets do not track balances. Use an RPC provider.',
|
|
245
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get token balance
|
|
251
|
+
*
|
|
252
|
+
* Note: Hardware wallets don't track balances - this requires RPC.
|
|
253
|
+
*/
|
|
254
|
+
async getTokenBalance(_asset: Asset): Promise<bigint> {
|
|
255
|
+
throw new WalletError(
|
|
256
|
+
'Hardware wallets do not track balances. Use an RPC provider.',
|
|
257
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── Account Management ─────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get multiple accounts from device
|
|
265
|
+
*/
|
|
266
|
+
async getAccounts(startIndex: number = 0, count: number = 5): Promise<HardwareAccount[]> {
|
|
267
|
+
this.requireConnected()
|
|
268
|
+
|
|
269
|
+
const accounts: HardwareAccount[] = []
|
|
270
|
+
|
|
271
|
+
for (let i = startIndex; i < startIndex + count; i++) {
|
|
272
|
+
const path = getDerivationPath(this.chain, i)
|
|
273
|
+
const account = await this.getAccountAtPath(path, i)
|
|
274
|
+
accounts.push(account)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return accounts
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Switch to different account index
|
|
282
|
+
*/
|
|
283
|
+
async switchAccount(accountIndex: number): Promise<HardwareAccount> {
|
|
284
|
+
this.requireConnected()
|
|
285
|
+
|
|
286
|
+
this._derivationPath = getDerivationPath(this.chain, accountIndex)
|
|
287
|
+
this._account = await this.getAccountFromDevice()
|
|
288
|
+
|
|
289
|
+
const previousAddress = this._address
|
|
290
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
291
|
+
|
|
292
|
+
if (previousAddress !== this._account.address) {
|
|
293
|
+
this.emitAccountChanged(previousAddress, this._account.address)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this._account
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─── Private Methods ────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Load WebUSB transport dynamically
|
|
303
|
+
*/
|
|
304
|
+
private async loadTransport(): Promise<TransportWebUSBType> {
|
|
305
|
+
try {
|
|
306
|
+
// @ts-expect-error - Dynamic import
|
|
307
|
+
const module = await import('@ledgerhq/hw-transport-webusb')
|
|
308
|
+
return module.default
|
|
309
|
+
} catch {
|
|
310
|
+
throw new HardwareWalletError(
|
|
311
|
+
'Failed to load Ledger transport. Install @ledgerhq/hw-transport-webusb',
|
|
312
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
313
|
+
'ledger'
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Load chain-specific Ledger app
|
|
320
|
+
*/
|
|
321
|
+
private async loadApp(): Promise<LedgerApp> {
|
|
322
|
+
if (!this.transport) {
|
|
323
|
+
throw new HardwareWalletError(
|
|
324
|
+
'Transport not connected',
|
|
325
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
326
|
+
'ledger'
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
if (this.chain === 'ethereum') {
|
|
332
|
+
// @ts-expect-error - Dynamic import
|
|
333
|
+
const module = await import('@ledgerhq/hw-app-eth')
|
|
334
|
+
return new module.default(this.transport)
|
|
335
|
+
} else if (this.chain === 'solana') {
|
|
336
|
+
// @ts-expect-error - Dynamic import
|
|
337
|
+
const module = await import('@ledgerhq/hw-app-solana')
|
|
338
|
+
return new module.default(this.transport)
|
|
339
|
+
} else {
|
|
340
|
+
throw new HardwareWalletError(
|
|
341
|
+
`Chain ${this.chain} not supported by Ledger adapter`,
|
|
342
|
+
HardwareErrorCode.UNSUPPORTED,
|
|
343
|
+
'ledger'
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof HardwareWalletError) throw error
|
|
348
|
+
|
|
349
|
+
throw new HardwareWalletError(
|
|
350
|
+
`Failed to load Ledger app. Install @ledgerhq/hw-app-${this.chain}`,
|
|
351
|
+
HardwareErrorCode.APP_NOT_OPEN,
|
|
352
|
+
'ledger',
|
|
353
|
+
error
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get app name for current chain
|
|
360
|
+
*/
|
|
361
|
+
private getAppName(): string {
|
|
362
|
+
switch (this.chain) {
|
|
363
|
+
case 'ethereum':
|
|
364
|
+
return 'Ethereum'
|
|
365
|
+
case 'solana':
|
|
366
|
+
return 'Solana'
|
|
367
|
+
default:
|
|
368
|
+
return 'Unknown'
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get account from device at current derivation path
|
|
374
|
+
*/
|
|
375
|
+
private async getAccountFromDevice(): Promise<HardwareAccount> {
|
|
376
|
+
return this.getAccountAtPath(this._derivationPath, this.config.accountIndex ?? 0)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get account at specific derivation path
|
|
381
|
+
*/
|
|
382
|
+
private async getAccountAtPath(path: string, index: number): Promise<HardwareAccount> {
|
|
383
|
+
if (!this.app) {
|
|
384
|
+
throw new HardwareWalletError(
|
|
385
|
+
'App not loaded',
|
|
386
|
+
HardwareErrorCode.APP_NOT_OPEN,
|
|
387
|
+
'ledger'
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
if (this.chain === 'ethereum') {
|
|
393
|
+
const ethApp = this.app as EthApp
|
|
394
|
+
const { address, publicKey } = await ethApp.getAddress(path)
|
|
395
|
+
return {
|
|
396
|
+
address,
|
|
397
|
+
publicKey: `0x${publicKey}` as HexString,
|
|
398
|
+
derivationPath: path,
|
|
399
|
+
index,
|
|
400
|
+
chain: this.chain,
|
|
401
|
+
}
|
|
402
|
+
} else if (this.chain === 'solana') {
|
|
403
|
+
const solApp = this.app as unknown as SolanaApp
|
|
404
|
+
const { address } = await solApp.getAddress(path)
|
|
405
|
+
return {
|
|
406
|
+
address: address.toString(),
|
|
407
|
+
publicKey: address.toString() as HexString, // Base58 for Solana
|
|
408
|
+
derivationPath: path,
|
|
409
|
+
index,
|
|
410
|
+
chain: this.chain,
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
throw new HardwareWalletError(
|
|
415
|
+
`Unsupported chain: ${this.chain}`,
|
|
416
|
+
HardwareErrorCode.UNSUPPORTED,
|
|
417
|
+
'ledger'
|
|
418
|
+
)
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw this.handleError(error)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Sign message on device
|
|
426
|
+
*/
|
|
427
|
+
private async signMessageOnDevice(message: Uint8Array): Promise<HardwareSignature> {
|
|
428
|
+
if (this.chain === 'ethereum') {
|
|
429
|
+
const ethApp = this.app as EthApp
|
|
430
|
+
const messageHex = Buffer.from(message).toString('hex')
|
|
431
|
+
const result = await ethApp.signPersonalMessage(this._derivationPath, messageHex)
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
r: `0x${result.r}` as HexString,
|
|
435
|
+
s: `0x${result.s}` as HexString,
|
|
436
|
+
v: result.v,
|
|
437
|
+
signature: `0x${result.r}${result.s}${result.v.toString(16).padStart(2, '0')}` as HexString,
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (this.chain === 'solana') {
|
|
442
|
+
const solApp = this.app as unknown as SolanaApp
|
|
443
|
+
const result = await solApp.signOffchainMessage(this._derivationPath, message)
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
r: '0x' as HexString,
|
|
447
|
+
s: '0x' as HexString,
|
|
448
|
+
v: 0,
|
|
449
|
+
signature: `0x${Buffer.from(result.signature).toString('hex')}` as HexString,
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
throw new HardwareWalletError(
|
|
454
|
+
`Message signing not supported for ${this.chain}`,
|
|
455
|
+
HardwareErrorCode.UNSUPPORTED,
|
|
456
|
+
'ledger'
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Sign transaction on device
|
|
462
|
+
*/
|
|
463
|
+
private async signTransactionOnDevice(tx: UnsignedTransaction): Promise<HardwareSignature> {
|
|
464
|
+
if (this.chain === 'ethereum') {
|
|
465
|
+
const ethApp = this.app as EthApp
|
|
466
|
+
const ethTx = tx.data as HardwareEthereumTx
|
|
467
|
+
|
|
468
|
+
// Build raw transaction for signing
|
|
469
|
+
const rawTx = this.buildRawEthereumTx(ethTx)
|
|
470
|
+
const result = await ethApp.signTransaction(this._derivationPath, rawTx)
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
r: `0x${result.r}` as HexString,
|
|
474
|
+
s: `0x${result.s}` as HexString,
|
|
475
|
+
v: parseInt(result.v, 16),
|
|
476
|
+
signature: `0x${result.r}${result.s}${result.v}` as HexString,
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (this.chain === 'solana') {
|
|
481
|
+
const solApp = this.app as unknown as SolanaApp
|
|
482
|
+
const txData = tx.data as Uint8Array
|
|
483
|
+
const result = await solApp.signTransaction(this._derivationPath, txData)
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
r: '0x' as HexString,
|
|
487
|
+
s: '0x' as HexString,
|
|
488
|
+
v: 0,
|
|
489
|
+
signature: `0x${Buffer.from(result.signature).toString('hex')}` as HexString,
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
throw new HardwareWalletError(
|
|
494
|
+
`Transaction signing not supported for ${this.chain}`,
|
|
495
|
+
HardwareErrorCode.UNSUPPORTED,
|
|
496
|
+
'ledger'
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Build raw Ethereum transaction for Ledger signing
|
|
502
|
+
*/
|
|
503
|
+
private buildRawEthereumTx(tx: HardwareEthereumTx): string {
|
|
504
|
+
// Simplified - actual implementation would use RLP encoding
|
|
505
|
+
// This is a placeholder for the structure
|
|
506
|
+
const fields = [
|
|
507
|
+
tx.nonce,
|
|
508
|
+
tx.gasPrice ?? tx.maxFeePerGas ?? '0x0',
|
|
509
|
+
tx.gasLimit,
|
|
510
|
+
tx.to,
|
|
511
|
+
tx.value,
|
|
512
|
+
tx.data ?? '0x',
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
return fields.join('').replace(/0x/g, '')
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Handle and transform errors
|
|
520
|
+
*/
|
|
521
|
+
private handleError(error: unknown): Error {
|
|
522
|
+
if (error instanceof HardwareWalletError) {
|
|
523
|
+
return error
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (error instanceof WalletError) {
|
|
527
|
+
return error
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const err = error as { statusCode?: number; message?: string; name?: string }
|
|
531
|
+
|
|
532
|
+
// Ledger-specific error codes
|
|
533
|
+
if (err.statusCode) {
|
|
534
|
+
switch (err.statusCode) {
|
|
535
|
+
case 0x6985: // User rejected
|
|
536
|
+
return new HardwareWalletError(
|
|
537
|
+
'Transaction rejected on device',
|
|
538
|
+
HardwareErrorCode.USER_REJECTED,
|
|
539
|
+
'ledger'
|
|
540
|
+
)
|
|
541
|
+
case 0x6a80: // Invalid data
|
|
542
|
+
return new HardwareWalletError(
|
|
543
|
+
'Invalid transaction data',
|
|
544
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
545
|
+
'ledger'
|
|
546
|
+
)
|
|
547
|
+
case 0x6b00: // Wrong app
|
|
548
|
+
return new HardwareWalletError(
|
|
549
|
+
`Please open the ${this.getAppName()} app on your Ledger`,
|
|
550
|
+
HardwareErrorCode.APP_NOT_OPEN,
|
|
551
|
+
'ledger'
|
|
552
|
+
)
|
|
553
|
+
case 0x6f00: // Technical error
|
|
554
|
+
return new HardwareWalletError(
|
|
555
|
+
'Device error. Try reconnecting.',
|
|
556
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
557
|
+
'ledger'
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Generic errors
|
|
563
|
+
if (err.name === 'TransportOpenUserCancelled') {
|
|
564
|
+
return new HardwareWalletError(
|
|
565
|
+
'Device selection cancelled',
|
|
566
|
+
HardwareErrorCode.USER_REJECTED,
|
|
567
|
+
'ledger'
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (err.message?.includes('No device selected')) {
|
|
572
|
+
return new HardwareWalletError(
|
|
573
|
+
'No Ledger device selected',
|
|
574
|
+
HardwareErrorCode.DEVICE_NOT_FOUND,
|
|
575
|
+
'ledger'
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return new HardwareWalletError(
|
|
580
|
+
err.message ?? 'Unknown Ledger error',
|
|
581
|
+
HardwareErrorCode.TRANSPORT_ERROR,
|
|
582
|
+
'ledger',
|
|
583
|
+
error
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ─── Type Stubs for Dynamic Imports ───────────────────────────────────────────
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Stub type for @ledgerhq/hw-transport-webusb
|
|
592
|
+
*/
|
|
593
|
+
interface TransportWebUSBType {
|
|
594
|
+
create(): Promise<HardwareTransport>
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Generic Ledger app interface
|
|
599
|
+
*/
|
|
600
|
+
interface LedgerApp {
|
|
601
|
+
getAddress(path: string): Promise<{ address: string; publicKey: string }>
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Ethereum Ledger app interface
|
|
606
|
+
*/
|
|
607
|
+
interface EthApp extends LedgerApp {
|
|
608
|
+
signPersonalMessage(path: string, messageHex: string): Promise<{ r: string; s: string; v: number }>
|
|
609
|
+
signTransaction(path: string, rawTx: string): Promise<{ r: string; s: string; v: string }>
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Solana Ledger app interface
|
|
614
|
+
*/
|
|
615
|
+
interface SolanaApp {
|
|
616
|
+
getAddress(path: string): Promise<{ address: Uint8Array }>
|
|
617
|
+
signTransaction(path: string, transaction: Uint8Array): Promise<{ signature: Uint8Array }>
|
|
618
|
+
signOffchainMessage(path: string, message: Uint8Array): Promise<{ signature: Uint8Array }>
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ─── Factory Function ─────────────────────────────────────────────────────────
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Create a Ledger wallet adapter
|
|
625
|
+
*/
|
|
626
|
+
export function createLedgerAdapter(config: LedgerConfig): LedgerWalletAdapter {
|
|
627
|
+
return new LedgerWalletAdapter(config)
|
|
628
|
+
}
|