@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.
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Hardware Wallet Types
3
+ *
4
+ * Type definitions for hardware wallet adapters (Ledger, Trezor).
5
+ */
6
+
7
+ import type { ChainId, HexString } from '@sip-protocol/types'
8
+
9
+ // ─── Device Types ───────────────────────────────────────────────────────────────
10
+
11
+ /**
12
+ * Supported hardware wallet manufacturers
13
+ */
14
+ export type HardwareWalletType = 'ledger' | 'trezor'
15
+
16
+ /**
17
+ * Ledger device models
18
+ */
19
+ export type LedgerModel = 'nanoS' | 'nanoSPlus' | 'nanoX' | 'stax' | 'flex'
20
+
21
+ /**
22
+ * Trezor device models
23
+ */
24
+ export type TrezorModel = 'one' | 'T' | 'safe3' | 'safe5'
25
+
26
+ /**
27
+ * Hardware wallet connection status
28
+ */
29
+ export type HardwareConnectionStatus =
30
+ | 'disconnected'
31
+ | 'connecting'
32
+ | 'connected'
33
+ | 'locked'
34
+ | 'app_closed'
35
+ | 'error'
36
+
37
+ /**
38
+ * Transport type for device communication
39
+ */
40
+ export type TransportType = 'usb' | 'bluetooth' | 'webusb' | 'webhid'
41
+
42
+ // ─── Device Info ────────────────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Hardware device information
46
+ */
47
+ export interface HardwareDeviceInfo {
48
+ /** Device manufacturer */
49
+ manufacturer: HardwareWalletType
50
+ /** Device model */
51
+ model: string
52
+ /** Firmware version */
53
+ firmwareVersion?: string
54
+ /** Whether device is locked */
55
+ isLocked: boolean
56
+ /** Currently open app (if any) */
57
+ currentApp?: string
58
+ /** Device label/name (user-set) */
59
+ label?: string
60
+ /** Device ID (unique identifier) */
61
+ deviceId?: string
62
+ }
63
+
64
+ // ─── Derivation Paths ───────────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * Standard BIP44 derivation paths
68
+ */
69
+ export const DerivationPath = {
70
+ /** Ethereum: m/44'/60'/0'/0/index */
71
+ ETHEREUM: "m/44'/60'/0'/0",
72
+ /** Ethereum (Ledger Live): m/44'/60'/index'/0/0 */
73
+ ETHEREUM_LEDGER_LIVE: "m/44'/60'",
74
+ /** Solana: m/44'/501'/0'/0' */
75
+ SOLANA: "m/44'/501'/0'/0'",
76
+ /** Bitcoin: m/84'/0'/0'/0 (native segwit) */
77
+ BITCOIN: "m/84'/0'/0'/0",
78
+ /** NEAR: m/44'/397'/0' */
79
+ NEAR: "m/44'/397'/0'",
80
+ } as const
81
+
82
+ /**
83
+ * Get derivation path for a chain
84
+ */
85
+ export function getDerivationPath(chain: ChainId, accountIndex: number = 0): string {
86
+ switch (chain) {
87
+ case 'ethereum':
88
+ return `${DerivationPath.ETHEREUM}/${accountIndex}`
89
+ case 'solana':
90
+ return DerivationPath.SOLANA.replace("0'", `${accountIndex}'`)
91
+ case 'bitcoin':
92
+ return `${DerivationPath.BITCOIN}/${accountIndex}`
93
+ case 'near':
94
+ return DerivationPath.NEAR.replace("0'", `${accountIndex}'`)
95
+ default:
96
+ return `${DerivationPath.ETHEREUM}/${accountIndex}`
97
+ }
98
+ }
99
+
100
+ // ─── Configuration ──────────────────────────────────────────────────────────────
101
+
102
+ /**
103
+ * Base hardware wallet configuration
104
+ */
105
+ export interface HardwareWalletConfig {
106
+ /** Target chain */
107
+ chain: ChainId
108
+ /** Account index (default: 0) */
109
+ accountIndex?: number
110
+ /** Custom derivation path (overrides default) */
111
+ derivationPath?: string
112
+ /** Transport type preference */
113
+ transport?: TransportType
114
+ /** Connection timeout in ms (default: 30000) */
115
+ timeout?: number
116
+ }
117
+
118
+ /**
119
+ * Ledger-specific configuration
120
+ */
121
+ export interface LedgerConfig extends HardwareWalletConfig {
122
+ /** Expected app name to be open on device */
123
+ appName?: string
124
+ /** Whether to use Ledger Live derivation path */
125
+ useLedgerLive?: boolean
126
+ /** Scramble key for transport */
127
+ scrambleKey?: string
128
+ }
129
+
130
+ /**
131
+ * Trezor-specific configuration
132
+ */
133
+ export interface TrezorConfig extends HardwareWalletConfig {
134
+ /** Trezor Connect manifest URL */
135
+ manifestUrl?: string
136
+ /** Trezor Connect manifest email */
137
+ manifestEmail?: string
138
+ /** Trezor Connect manifest app name */
139
+ manifestAppName?: string
140
+ /** Whether to use popup for Trezor Connect */
141
+ popup?: boolean
142
+ }
143
+
144
+ // ─── Signing Requests ───────────────────────────────────────────────────────────
145
+
146
+ /**
147
+ * Hardware wallet signing request
148
+ */
149
+ export interface HardwareSignRequest {
150
+ /** Raw message to sign */
151
+ message: Uint8Array
152
+ /** Display message on device (if supported) */
153
+ displayMessage?: string
154
+ /** Whether this is a transaction (vs arbitrary message) */
155
+ isTransaction?: boolean
156
+ }
157
+
158
+ /**
159
+ * Ethereum transaction for hardware signing
160
+ */
161
+ export interface HardwareEthereumTx {
162
+ /** Recipient address */
163
+ to: string
164
+ /** Value in wei (hex) */
165
+ value: HexString
166
+ /** Gas limit (hex) */
167
+ gasLimit: HexString
168
+ /** Gas price (hex) - for legacy tx */
169
+ gasPrice?: HexString
170
+ /** Max fee per gas (hex) - for EIP-1559 */
171
+ maxFeePerGas?: HexString
172
+ /** Max priority fee per gas (hex) - for EIP-1559 */
173
+ maxPriorityFeePerGas?: HexString
174
+ /** Transaction data (hex) */
175
+ data?: HexString
176
+ /** Nonce (hex) */
177
+ nonce: HexString
178
+ /** Chain ID */
179
+ chainId: number
180
+ }
181
+
182
+ /**
183
+ * Signature from hardware wallet
184
+ */
185
+ export interface HardwareSignature {
186
+ /** r component */
187
+ r: HexString
188
+ /** s component */
189
+ s: HexString
190
+ /** v component (recovery id) */
191
+ v: number
192
+ /** Full signature (r + s + v) */
193
+ signature: HexString
194
+ }
195
+
196
+ // ─── Account Info ───────────────────────────────────────────────────────────────
197
+
198
+ /**
199
+ * Account derived from hardware wallet
200
+ */
201
+ export interface HardwareAccount {
202
+ /** Account address */
203
+ address: string
204
+ /** Public key (hex) */
205
+ publicKey: HexString
206
+ /** Derivation path used */
207
+ derivationPath: string
208
+ /** Account index */
209
+ index: number
210
+ /** Chain */
211
+ chain: ChainId
212
+ }
213
+
214
+ // ─── Transport Interfaces ───────────────────────────────────────────────────────
215
+
216
+ /**
217
+ * Abstract transport interface for hardware communication
218
+ *
219
+ * This is implemented by actual transport libraries:
220
+ * - @ledgerhq/hw-transport-webusb
221
+ * - @ledgerhq/hw-transport-webhid
222
+ * - @trezor/connect-web
223
+ */
224
+ export interface HardwareTransport {
225
+ /** Open connection to device */
226
+ open(): Promise<void>
227
+ /** Close connection */
228
+ close(): Promise<void>
229
+ /** Send APDU command (Ledger) */
230
+ send?(cla: number, ins: number, p1: number, p2: number, data?: Buffer): Promise<Buffer>
231
+ /** Check if transport is open */
232
+ isOpen?: boolean
233
+ }
234
+
235
+ // ─── Error Types ────────────────────────────────────────────────────────────────
236
+
237
+ /**
238
+ * Hardware wallet error codes
239
+ */
240
+ export const HardwareErrorCode = {
241
+ /** Device not found/connected */
242
+ DEVICE_NOT_FOUND: 'HARDWARE_DEVICE_NOT_FOUND',
243
+ /** Device is locked (requires PIN) */
244
+ DEVICE_LOCKED: 'HARDWARE_DEVICE_LOCKED',
245
+ /** Required app not open on device */
246
+ APP_NOT_OPEN: 'HARDWARE_APP_NOT_OPEN',
247
+ /** User rejected on device */
248
+ USER_REJECTED: 'HARDWARE_USER_REJECTED',
249
+ /** Transport/communication error */
250
+ TRANSPORT_ERROR: 'HARDWARE_TRANSPORT_ERROR',
251
+ /** Timeout waiting for device */
252
+ TIMEOUT: 'HARDWARE_TIMEOUT',
253
+ /** Unsupported operation */
254
+ UNSUPPORTED: 'HARDWARE_UNSUPPORTED',
255
+ /** Invalid derivation path */
256
+ INVALID_PATH: 'HARDWARE_INVALID_PATH',
257
+ } as const
258
+
259
+ export type HardwareErrorCodeType = typeof HardwareErrorCode[keyof typeof HardwareErrorCode]
260
+
261
+ /**
262
+ * Hardware wallet error
263
+ */
264
+ export class HardwareWalletError extends Error {
265
+ readonly code: HardwareErrorCodeType
266
+ readonly device?: HardwareWalletType
267
+ readonly details?: unknown
268
+
269
+ constructor(
270
+ message: string,
271
+ code: HardwareErrorCodeType,
272
+ device?: HardwareWalletType,
273
+ details?: unknown
274
+ ) {
275
+ super(message)
276
+ this.name = 'HardwareWalletError'
277
+ this.code = code
278
+ this.device = device
279
+ this.details = details
280
+ }
281
+ }
282
+
283
+ // ─── Utilities ──────────────────────────────────────────────────────────────────
284
+
285
+ /**
286
+ * Check if browser supports WebUSB
287
+ */
288
+ export function supportsWebUSB(): boolean {
289
+ return typeof navigator !== 'undefined' && 'usb' in navigator
290
+ }
291
+
292
+ /**
293
+ * Check if browser supports WebHID
294
+ */
295
+ export function supportsWebHID(): boolean {
296
+ return typeof navigator !== 'undefined' && 'hid' in navigator
297
+ }
298
+
299
+ /**
300
+ * Check if browser supports Web Bluetooth
301
+ */
302
+ export function supportsWebBluetooth(): boolean {
303
+ return typeof navigator !== 'undefined' && 'bluetooth' in navigator
304
+ }
305
+
306
+ /**
307
+ * Get available transport types
308
+ */
309
+ export function getAvailableTransports(): TransportType[] {
310
+ const transports: TransportType[] = []
311
+
312
+ if (supportsWebUSB()) transports.push('webusb')
313
+ if (supportsWebHID()) transports.push('webhid')
314
+ if (supportsWebBluetooth()) transports.push('bluetooth')
315
+
316
+ return transports
317
+ }
@@ -84,6 +84,46 @@ export type {
84
84
  MockEthereumAdapterConfig,
85
85
  } from './ethereum'
86
86
 
87
+ // Hardware wallet adapters
88
+ export {
89
+ // Types
90
+ type HardwareWalletType,
91
+ type LedgerModel,
92
+ type TrezorModel,
93
+ type HardwareConnectionStatus,
94
+ type TransportType,
95
+ type HardwareDeviceInfo,
96
+ type HardwareWalletConfig,
97
+ type LedgerConfig,
98
+ type TrezorConfig,
99
+ type HardwareSignRequest,
100
+ type HardwareEthereumTx,
101
+ type HardwareSignature,
102
+ type HardwareAccount,
103
+ type HardwareTransport,
104
+ type HardwareErrorCodeType,
105
+ type MockHardwareConfig,
106
+ HardwareErrorCode,
107
+ HardwareWalletError,
108
+ DerivationPath,
109
+ getDerivationPath,
110
+ supportsWebUSB,
111
+ supportsWebHID,
112
+ supportsWebBluetooth,
113
+ getAvailableTransports,
114
+ // Ledger
115
+ LedgerWalletAdapter,
116
+ createLedgerAdapter,
117
+ // Trezor
118
+ TrezorWalletAdapter,
119
+ createTrezorAdapter,
120
+ // Mocks
121
+ MockLedgerAdapter,
122
+ MockTrezorAdapter,
123
+ createMockLedgerAdapter,
124
+ createMockTrezorAdapter,
125
+ } from './hardware'
126
+
87
127
  // Re-export types from types package for convenience
88
128
  export type {
89
129
  // Core types
@@ -369,11 +369,14 @@ export class ZcashShieldedService {
369
369
  )
370
370
  }
371
371
 
372
+ // Get fee from operation result, fall back to params.fee or estimate
373
+ const fee = operation.result.fee ?? params.fee ?? this.estimateFee(1)
374
+
372
375
  return {
373
376
  txid: operation.result.txid,
374
377
  operationId,
375
378
  amount: params.amount,
376
- fee: params.fee ?? 0, // TODO: Get actual fee from operation
379
+ fee,
377
380
  to: params.to,
378
381
  from: fromAddress,
379
382
  timestamp: Math.floor(Date.now() / 1000),
@@ -561,6 +564,61 @@ export class ZcashShieldedService {
561
564
  return this.client.isTestnet
562
565
  }
563
566
 
567
+ // ─── Fee Estimation ──────────────────────────────────────────────────────────
568
+
569
+ /**
570
+ * ZIP-317 fee constants (in ZEC)
571
+ * @see https://zips.z.cash/zip-0317
572
+ */
573
+ private static readonly ZIP317_MARGINAL_FEE = 0.00005 // 5000 zatoshis per logical action
574
+ private static readonly ZIP317_GRACE_ACTIONS = 2 // First 2 actions are free
575
+ private static readonly ZIP317_P2PKH_STANDARD_INPUT_SIZE = 150
576
+ private static readonly ZIP317_P2PKH_STANDARD_OUTPUT_SIZE = 34
577
+
578
+ /**
579
+ * Estimate transaction fee based on ZIP-317 conventional fee
580
+ *
581
+ * The ZIP-317 fee is calculated as:
582
+ * fee = marginal_fee * max(grace_actions, logical_actions)
583
+ *
584
+ * For shielded transactions:
585
+ * - Each Sapling spend = 1 logical action
586
+ * - Each Sapling output = 1 logical action
587
+ * - Each Orchard action = 1 logical action (covers both spend and output)
588
+ *
589
+ * @param recipients - Number of recipients (outputs)
590
+ * @param inputs - Estimated number of input notes (default: 1)
591
+ * @returns Estimated fee in ZEC
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * // Estimate fee for 1 recipient
596
+ * const fee = service.estimateFee(1)
597
+ *
598
+ * // Estimate fee for 3 recipients with 2 input notes
599
+ * const fee = service.estimateFee(3, 2)
600
+ * ```
601
+ */
602
+ estimateFee(recipients: number = 1, inputs: number = 1): number {
603
+ // For shielded transactions, logical actions = inputs + outputs
604
+ // Plus 1 for change output in most cases
605
+ const logicalActions = inputs + recipients + 1
606
+
607
+ // ZIP-317: fee = marginal_fee * max(grace_actions, logical_actions)
608
+ const billableActions = Math.max(ZcashShieldedService.ZIP317_GRACE_ACTIONS, logicalActions)
609
+
610
+ return billableActions * ZcashShieldedService.ZIP317_MARGINAL_FEE
611
+ }
612
+
613
+ /**
614
+ * Get minimum fee for a shielded transaction
615
+ *
616
+ * @returns Minimum fee in ZEC (ZIP-317 with grace actions)
617
+ */
618
+ getMinimumFee(): number {
619
+ return ZcashShieldedService.ZIP317_GRACE_ACTIONS * ZcashShieldedService.ZIP317_MARGINAL_FEE
620
+ }
621
+
564
622
  // ─── Helpers ─────────────────────────────────────────────────────────────────
565
623
 
566
624
  /**