@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,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Hardware Wallet Adapters
|
|
3
|
+
*
|
|
4
|
+
* Mock implementations of hardware wallet adapters for testing.
|
|
5
|
+
* These simulate Ledger and Trezor device behavior without actual hardware.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ChainId,
|
|
10
|
+
HexString,
|
|
11
|
+
Asset,
|
|
12
|
+
Signature,
|
|
13
|
+
UnsignedTransaction,
|
|
14
|
+
SignedTransaction,
|
|
15
|
+
TransactionReceipt,
|
|
16
|
+
} from '@sip-protocol/types'
|
|
17
|
+
import { WalletErrorCode } from '@sip-protocol/types'
|
|
18
|
+
import { bytesToHex, randomBytes } from '@noble/hashes/utils'
|
|
19
|
+
import { BaseWalletAdapter } from '../base-adapter'
|
|
20
|
+
import { WalletError } from '../errors'
|
|
21
|
+
import {
|
|
22
|
+
type HardwareWalletConfig,
|
|
23
|
+
type HardwareDeviceInfo,
|
|
24
|
+
type HardwareAccount,
|
|
25
|
+
type HardwareWalletType,
|
|
26
|
+
type LedgerModel,
|
|
27
|
+
type TrezorModel,
|
|
28
|
+
HardwareErrorCode,
|
|
29
|
+
HardwareWalletError,
|
|
30
|
+
getDerivationPath,
|
|
31
|
+
} from './types'
|
|
32
|
+
|
|
33
|
+
// ─── Mock Configuration ─────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Mock hardware wallet configuration
|
|
37
|
+
*/
|
|
38
|
+
export interface MockHardwareConfig extends HardwareWalletConfig {
|
|
39
|
+
/** Device type to simulate */
|
|
40
|
+
deviceType: HardwareWalletType
|
|
41
|
+
/** Device model */
|
|
42
|
+
model?: LedgerModel | TrezorModel
|
|
43
|
+
/** Simulate device locked state */
|
|
44
|
+
isLocked?: boolean
|
|
45
|
+
/** Simulate signing delay (ms) */
|
|
46
|
+
signingDelay?: number
|
|
47
|
+
/** Simulate user rejection */
|
|
48
|
+
shouldReject?: boolean
|
|
49
|
+
/** Simulate connection failure */
|
|
50
|
+
shouldFailConnect?: boolean
|
|
51
|
+
/** Mock address to return */
|
|
52
|
+
mockAddress?: string
|
|
53
|
+
/** Mock public key to return */
|
|
54
|
+
mockPublicKey?: HexString
|
|
55
|
+
/** Number of accounts available */
|
|
56
|
+
accountCount?: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mock Ledger adapter for testing
|
|
61
|
+
*/
|
|
62
|
+
export class MockLedgerAdapter extends BaseWalletAdapter {
|
|
63
|
+
readonly chain: ChainId
|
|
64
|
+
readonly name: string = 'mock-ledger'
|
|
65
|
+
|
|
66
|
+
private config: MockHardwareConfig
|
|
67
|
+
private _derivationPath: string
|
|
68
|
+
private _deviceInfo: HardwareDeviceInfo | null = null
|
|
69
|
+
private _account: HardwareAccount | null = null
|
|
70
|
+
private mockAccounts: HardwareAccount[] = []
|
|
71
|
+
|
|
72
|
+
constructor(config: MockHardwareConfig) {
|
|
73
|
+
super()
|
|
74
|
+
this.chain = config.chain
|
|
75
|
+
this.config = {
|
|
76
|
+
model: 'nanoX',
|
|
77
|
+
accountIndex: 0,
|
|
78
|
+
isLocked: false,
|
|
79
|
+
signingDelay: 100,
|
|
80
|
+
shouldReject: false,
|
|
81
|
+
shouldFailConnect: false,
|
|
82
|
+
accountCount: 5,
|
|
83
|
+
...config,
|
|
84
|
+
}
|
|
85
|
+
this._derivationPath = config.derivationPath ??
|
|
86
|
+
getDerivationPath(config.chain, config.accountIndex ?? 0)
|
|
87
|
+
|
|
88
|
+
// Generate mock accounts
|
|
89
|
+
this.generateMockAccounts()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get device information
|
|
94
|
+
*/
|
|
95
|
+
get deviceInfo(): HardwareDeviceInfo | null {
|
|
96
|
+
return this._deviceInfo
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get current derivation path
|
|
101
|
+
*/
|
|
102
|
+
get derivationPath(): string {
|
|
103
|
+
return this._derivationPath
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get current account
|
|
108
|
+
*/
|
|
109
|
+
get account(): HardwareAccount | null {
|
|
110
|
+
return this._account
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Connect to mock Ledger device
|
|
115
|
+
*/
|
|
116
|
+
async connect(): Promise<void> {
|
|
117
|
+
this._connectionState = 'connecting'
|
|
118
|
+
|
|
119
|
+
// Simulate connection delay
|
|
120
|
+
await this.delay(200)
|
|
121
|
+
|
|
122
|
+
if (this.config.shouldFailConnect) {
|
|
123
|
+
this._connectionState = 'error'
|
|
124
|
+
throw new HardwareWalletError(
|
|
125
|
+
'Mock connection failure',
|
|
126
|
+
HardwareErrorCode.DEVICE_NOT_FOUND,
|
|
127
|
+
'ledger'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (this.config.isLocked) {
|
|
132
|
+
this._connectionState = 'error'
|
|
133
|
+
throw new HardwareWalletError(
|
|
134
|
+
'Device is locked. Please enter PIN.',
|
|
135
|
+
HardwareErrorCode.DEVICE_LOCKED,
|
|
136
|
+
'ledger'
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this._account = this.mockAccounts[this.config.accountIndex ?? 0]
|
|
141
|
+
|
|
142
|
+
this._deviceInfo = {
|
|
143
|
+
manufacturer: 'ledger',
|
|
144
|
+
model: this.config.model as string ?? 'Nano X',
|
|
145
|
+
firmwareVersion: '2.1.0',
|
|
146
|
+
isLocked: false,
|
|
147
|
+
currentApp: this.getAppName(),
|
|
148
|
+
label: 'Mock Ledger',
|
|
149
|
+
deviceId: 'mock-ledger-001',
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Disconnect from mock device
|
|
157
|
+
*/
|
|
158
|
+
async disconnect(): Promise<void> {
|
|
159
|
+
this._account = null
|
|
160
|
+
this._deviceInfo = null
|
|
161
|
+
this.setDisconnected()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Sign a message
|
|
166
|
+
*/
|
|
167
|
+
async signMessage(message: Uint8Array): Promise<Signature> {
|
|
168
|
+
this.requireConnected()
|
|
169
|
+
|
|
170
|
+
// Simulate signing delay
|
|
171
|
+
await this.delay(this.config.signingDelay ?? 100)
|
|
172
|
+
|
|
173
|
+
if (this.config.shouldReject) {
|
|
174
|
+
throw new HardwareWalletError(
|
|
175
|
+
'User rejected on device',
|
|
176
|
+
HardwareErrorCode.USER_REJECTED,
|
|
177
|
+
'ledger'
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Generate mock signature
|
|
182
|
+
const sig = this.generateMockSignature(message)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
signature: sig,
|
|
186
|
+
publicKey: this._account!.publicKey,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Sign a transaction
|
|
192
|
+
*/
|
|
193
|
+
async signTransaction(tx: UnsignedTransaction): Promise<SignedTransaction> {
|
|
194
|
+
this.requireConnected()
|
|
195
|
+
|
|
196
|
+
await this.delay(this.config.signingDelay ?? 100)
|
|
197
|
+
|
|
198
|
+
if (this.config.shouldReject) {
|
|
199
|
+
throw new HardwareWalletError(
|
|
200
|
+
'Transaction rejected on device',
|
|
201
|
+
HardwareErrorCode.USER_REJECTED,
|
|
202
|
+
'ledger'
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const sig = this.generateMockSignature(
|
|
207
|
+
new TextEncoder().encode(JSON.stringify(tx.data))
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
unsigned: tx,
|
|
212
|
+
signatures: [
|
|
213
|
+
{
|
|
214
|
+
signature: sig,
|
|
215
|
+
publicKey: this._account!.publicKey,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
serialized: sig,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Sign and send transaction
|
|
224
|
+
*/
|
|
225
|
+
async signAndSendTransaction(tx: UnsignedTransaction): Promise<TransactionReceipt> {
|
|
226
|
+
const signed = await this.signTransaction(tx)
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
txHash: signed.serialized.slice(0, 66) as HexString,
|
|
230
|
+
status: 'pending',
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get balance (not supported by hardware wallets)
|
|
236
|
+
*/
|
|
237
|
+
async getBalance(): Promise<bigint> {
|
|
238
|
+
throw new WalletError(
|
|
239
|
+
'Hardware wallets do not track balances',
|
|
240
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get token balance (not supported by hardware wallets)
|
|
246
|
+
*/
|
|
247
|
+
async getTokenBalance(_asset: Asset): Promise<bigint> {
|
|
248
|
+
throw new WalletError(
|
|
249
|
+
'Hardware wallets do not track balances',
|
|
250
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get multiple accounts
|
|
256
|
+
*/
|
|
257
|
+
async getAccounts(startIndex: number = 0, count: number = 5): Promise<HardwareAccount[]> {
|
|
258
|
+
this.requireConnected()
|
|
259
|
+
return this.mockAccounts.slice(startIndex, startIndex + count)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Switch account
|
|
264
|
+
*/
|
|
265
|
+
async switchAccount(accountIndex: number): Promise<HardwareAccount> {
|
|
266
|
+
this.requireConnected()
|
|
267
|
+
|
|
268
|
+
if (accountIndex >= this.mockAccounts.length) {
|
|
269
|
+
throw new HardwareWalletError(
|
|
270
|
+
'Account index out of range',
|
|
271
|
+
HardwareErrorCode.INVALID_PATH,
|
|
272
|
+
'ledger'
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const previousAddress = this._address
|
|
277
|
+
this._account = this.mockAccounts[accountIndex]
|
|
278
|
+
this._derivationPath = this._account.derivationPath
|
|
279
|
+
|
|
280
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
281
|
+
|
|
282
|
+
if (previousAddress !== this._account.address) {
|
|
283
|
+
this.emitAccountChanged(previousAddress, this._account.address)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return this._account
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ─── Test Helpers ───────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Set whether device should reject signing
|
|
293
|
+
*/
|
|
294
|
+
setShouldReject(shouldReject: boolean): void {
|
|
295
|
+
this.config.shouldReject = shouldReject
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Set signing delay
|
|
300
|
+
*/
|
|
301
|
+
setSigningDelay(delay: number): void {
|
|
302
|
+
this.config.signingDelay = delay
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Simulate device lock
|
|
307
|
+
*/
|
|
308
|
+
simulateLock(): void {
|
|
309
|
+
if (this._deviceInfo) {
|
|
310
|
+
this._deviceInfo.isLocked = true
|
|
311
|
+
}
|
|
312
|
+
this.config.isLocked = true
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Simulate device unlock
|
|
317
|
+
*/
|
|
318
|
+
simulateUnlock(): void {
|
|
319
|
+
if (this._deviceInfo) {
|
|
320
|
+
this._deviceInfo.isLocked = false
|
|
321
|
+
}
|
|
322
|
+
this.config.isLocked = false
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ─── Private Methods ────────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
private getAppName(): string {
|
|
328
|
+
switch (this.chain) {
|
|
329
|
+
case 'ethereum':
|
|
330
|
+
return 'Ethereum'
|
|
331
|
+
case 'solana':
|
|
332
|
+
return 'Solana'
|
|
333
|
+
default:
|
|
334
|
+
return 'Unknown'
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private generateMockAccounts(): void {
|
|
339
|
+
const count = this.config.accountCount ?? 5
|
|
340
|
+
|
|
341
|
+
for (let i = 0; i < count; i++) {
|
|
342
|
+
const path = getDerivationPath(this.chain, i)
|
|
343
|
+
const address = this.config.mockAddress && i === 0
|
|
344
|
+
? this.config.mockAddress
|
|
345
|
+
: this.generateMockAddress(i)
|
|
346
|
+
const publicKey = this.config.mockPublicKey && i === 0
|
|
347
|
+
? this.config.mockPublicKey
|
|
348
|
+
: this.generateMockPublicKey(i)
|
|
349
|
+
|
|
350
|
+
this.mockAccounts.push({
|
|
351
|
+
address,
|
|
352
|
+
publicKey,
|
|
353
|
+
derivationPath: path,
|
|
354
|
+
index: i,
|
|
355
|
+
chain: this.chain,
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private generateMockAddress(index: number): string {
|
|
361
|
+
const bytes = randomBytes(20)
|
|
362
|
+
bytes[0] = index // Make addresses deterministic based on index
|
|
363
|
+
return `0x${bytesToHex(bytes)}`
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private generateMockPublicKey(index: number): HexString {
|
|
367
|
+
const bytes = randomBytes(33)
|
|
368
|
+
bytes[0] = 0x02 // Compressed public key prefix
|
|
369
|
+
bytes[1] = index
|
|
370
|
+
return `0x${bytesToHex(bytes)}` as HexString
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private generateMockSignature(data: Uint8Array): HexString {
|
|
374
|
+
const sig = new Uint8Array(65)
|
|
375
|
+
for (let i = 0; i < 32; i++) {
|
|
376
|
+
sig[i] = (data[i % data.length] ?? 0) ^ (i * 7) // r
|
|
377
|
+
sig[32 + i] = (data[i % data.length] ?? 0) ^ (i * 11) // s
|
|
378
|
+
}
|
|
379
|
+
sig[64] = 27 // v
|
|
380
|
+
return `0x${bytesToHex(sig)}` as HexString
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private delay(ms: number): Promise<void> {
|
|
384
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Mock Trezor adapter for testing
|
|
390
|
+
*/
|
|
391
|
+
export class MockTrezorAdapter extends BaseWalletAdapter {
|
|
392
|
+
readonly chain: ChainId
|
|
393
|
+
readonly name: string = 'mock-trezor'
|
|
394
|
+
|
|
395
|
+
private config: MockHardwareConfig
|
|
396
|
+
private _derivationPath: string
|
|
397
|
+
private _deviceInfo: HardwareDeviceInfo | null = null
|
|
398
|
+
private _account: HardwareAccount | null = null
|
|
399
|
+
private mockAccounts: HardwareAccount[] = []
|
|
400
|
+
|
|
401
|
+
constructor(config: MockHardwareConfig) {
|
|
402
|
+
super()
|
|
403
|
+
this.chain = config.chain
|
|
404
|
+
this.config = {
|
|
405
|
+
model: 'T',
|
|
406
|
+
accountIndex: 0,
|
|
407
|
+
isLocked: false,
|
|
408
|
+
signingDelay: 100,
|
|
409
|
+
shouldReject: false,
|
|
410
|
+
shouldFailConnect: false,
|
|
411
|
+
accountCount: 5,
|
|
412
|
+
...config,
|
|
413
|
+
}
|
|
414
|
+
this._derivationPath = config.derivationPath ??
|
|
415
|
+
getDerivationPath(config.chain, config.accountIndex ?? 0)
|
|
416
|
+
|
|
417
|
+
this.generateMockAccounts()
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
get deviceInfo(): HardwareDeviceInfo | null {
|
|
421
|
+
return this._deviceInfo
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
get derivationPath(): string {
|
|
425
|
+
return this._derivationPath
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
get account(): HardwareAccount | null {
|
|
429
|
+
return this._account
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async connect(): Promise<void> {
|
|
433
|
+
this._connectionState = 'connecting'
|
|
434
|
+
|
|
435
|
+
await this.delay(200)
|
|
436
|
+
|
|
437
|
+
if (this.config.shouldFailConnect) {
|
|
438
|
+
this._connectionState = 'error'
|
|
439
|
+
throw new HardwareWalletError(
|
|
440
|
+
'Mock connection failure',
|
|
441
|
+
HardwareErrorCode.DEVICE_NOT_FOUND,
|
|
442
|
+
'trezor'
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (this.config.isLocked) {
|
|
447
|
+
this._connectionState = 'error'
|
|
448
|
+
throw new HardwareWalletError(
|
|
449
|
+
'Device requires PIN',
|
|
450
|
+
HardwareErrorCode.DEVICE_LOCKED,
|
|
451
|
+
'trezor'
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this._account = this.mockAccounts[this.config.accountIndex ?? 0]
|
|
456
|
+
|
|
457
|
+
this._deviceInfo = {
|
|
458
|
+
manufacturer: 'trezor',
|
|
459
|
+
model: this.config.model as string ?? 'Model T',
|
|
460
|
+
firmwareVersion: '2.5.3',
|
|
461
|
+
isLocked: false,
|
|
462
|
+
label: 'Mock Trezor',
|
|
463
|
+
deviceId: 'mock-trezor-001',
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async disconnect(): Promise<void> {
|
|
470
|
+
this._account = null
|
|
471
|
+
this._deviceInfo = null
|
|
472
|
+
this.setDisconnected()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async signMessage(message: Uint8Array): Promise<Signature> {
|
|
476
|
+
this.requireConnected()
|
|
477
|
+
|
|
478
|
+
await this.delay(this.config.signingDelay ?? 100)
|
|
479
|
+
|
|
480
|
+
if (this.config.shouldReject) {
|
|
481
|
+
throw new HardwareWalletError(
|
|
482
|
+
'User rejected on device',
|
|
483
|
+
HardwareErrorCode.USER_REJECTED,
|
|
484
|
+
'trezor'
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const sig = this.generateMockSignature(message)
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
signature: sig,
|
|
492
|
+
publicKey: this._account!.publicKey,
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async signTransaction(tx: UnsignedTransaction): Promise<SignedTransaction> {
|
|
497
|
+
this.requireConnected()
|
|
498
|
+
|
|
499
|
+
await this.delay(this.config.signingDelay ?? 100)
|
|
500
|
+
|
|
501
|
+
if (this.config.shouldReject) {
|
|
502
|
+
throw new HardwareWalletError(
|
|
503
|
+
'Transaction rejected on device',
|
|
504
|
+
HardwareErrorCode.USER_REJECTED,
|
|
505
|
+
'trezor'
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const sig = this.generateMockSignature(
|
|
510
|
+
new TextEncoder().encode(JSON.stringify(tx.data))
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
unsigned: tx,
|
|
515
|
+
signatures: [
|
|
516
|
+
{
|
|
517
|
+
signature: sig,
|
|
518
|
+
publicKey: this._account!.publicKey,
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
serialized: sig,
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async signAndSendTransaction(tx: UnsignedTransaction): Promise<TransactionReceipt> {
|
|
526
|
+
const signed = await this.signTransaction(tx)
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
txHash: signed.serialized.slice(0, 66) as HexString,
|
|
530
|
+
status: 'pending',
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async getBalance(): Promise<bigint> {
|
|
535
|
+
throw new WalletError(
|
|
536
|
+
'Hardware wallets do not track balances',
|
|
537
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
538
|
+
)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async getTokenBalance(_asset: Asset): Promise<bigint> {
|
|
542
|
+
throw new WalletError(
|
|
543
|
+
'Hardware wallets do not track balances',
|
|
544
|
+
WalletErrorCode.UNSUPPORTED_OPERATION
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async getAccounts(startIndex: number = 0, count: number = 5): Promise<HardwareAccount[]> {
|
|
549
|
+
this.requireConnected()
|
|
550
|
+
return this.mockAccounts.slice(startIndex, startIndex + count)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async switchAccount(accountIndex: number): Promise<HardwareAccount> {
|
|
554
|
+
this.requireConnected()
|
|
555
|
+
|
|
556
|
+
if (accountIndex >= this.mockAccounts.length) {
|
|
557
|
+
throw new HardwareWalletError(
|
|
558
|
+
'Account index out of range',
|
|
559
|
+
HardwareErrorCode.INVALID_PATH,
|
|
560
|
+
'trezor'
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const previousAddress = this._address
|
|
565
|
+
this._account = this.mockAccounts[accountIndex]
|
|
566
|
+
this._derivationPath = this._account.derivationPath
|
|
567
|
+
|
|
568
|
+
this.setConnected(this._account.address, this._account.publicKey)
|
|
569
|
+
|
|
570
|
+
if (previousAddress !== this._account.address) {
|
|
571
|
+
this.emitAccountChanged(previousAddress, this._account.address)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return this._account
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
setShouldReject(shouldReject: boolean): void {
|
|
578
|
+
this.config.shouldReject = shouldReject
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
setSigningDelay(delay: number): void {
|
|
582
|
+
this.config.signingDelay = delay
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
simulateLock(): void {
|
|
586
|
+
if (this._deviceInfo) {
|
|
587
|
+
this._deviceInfo.isLocked = true
|
|
588
|
+
}
|
|
589
|
+
this.config.isLocked = true
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
simulateUnlock(): void {
|
|
593
|
+
if (this._deviceInfo) {
|
|
594
|
+
this._deviceInfo.isLocked = false
|
|
595
|
+
}
|
|
596
|
+
this.config.isLocked = false
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private generateMockAccounts(): void {
|
|
600
|
+
const count = this.config.accountCount ?? 5
|
|
601
|
+
|
|
602
|
+
for (let i = 0; i < count; i++) {
|
|
603
|
+
const path = getDerivationPath(this.chain, i)
|
|
604
|
+
const address = this.config.mockAddress && i === 0
|
|
605
|
+
? this.config.mockAddress
|
|
606
|
+
: this.generateMockAddress(i)
|
|
607
|
+
const publicKey = this.config.mockPublicKey && i === 0
|
|
608
|
+
? this.config.mockPublicKey
|
|
609
|
+
: this.generateMockPublicKey(i)
|
|
610
|
+
|
|
611
|
+
this.mockAccounts.push({
|
|
612
|
+
address,
|
|
613
|
+
publicKey,
|
|
614
|
+
derivationPath: path,
|
|
615
|
+
index: i,
|
|
616
|
+
chain: this.chain,
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private generateMockAddress(index: number): string {
|
|
622
|
+
const bytes = randomBytes(20)
|
|
623
|
+
bytes[0] = index + 100 // Different from Ledger mocks
|
|
624
|
+
return `0x${bytesToHex(bytes)}`
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
private generateMockPublicKey(index: number): HexString {
|
|
628
|
+
const bytes = randomBytes(33)
|
|
629
|
+
bytes[0] = 0x03 // Different compressed prefix
|
|
630
|
+
bytes[1] = index + 100
|
|
631
|
+
return `0x${bytesToHex(bytes)}` as HexString
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private generateMockSignature(data: Uint8Array): HexString {
|
|
635
|
+
const sig = new Uint8Array(65)
|
|
636
|
+
for (let i = 0; i < 32; i++) {
|
|
637
|
+
sig[i] = (data[i % data.length] ?? 0) ^ (i * 13)
|
|
638
|
+
sig[32 + i] = (data[i % data.length] ?? 0) ^ (i * 17)
|
|
639
|
+
}
|
|
640
|
+
sig[64] = 28
|
|
641
|
+
return `0x${bytesToHex(sig)}` as HexString
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private delay(ms: number): Promise<void> {
|
|
645
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ─── Factory Functions ────────────────────────────────────────────────────────
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Create a mock Ledger adapter
|
|
653
|
+
*/
|
|
654
|
+
export function createMockLedgerAdapter(
|
|
655
|
+
config: Omit<MockHardwareConfig, 'deviceType'>
|
|
656
|
+
): MockLedgerAdapter {
|
|
657
|
+
return new MockLedgerAdapter({ ...config, deviceType: 'ledger' })
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Create a mock Trezor adapter
|
|
662
|
+
*/
|
|
663
|
+
export function createMockTrezorAdapter(
|
|
664
|
+
config: Omit<MockHardwareConfig, 'deviceType'>
|
|
665
|
+
): MockTrezorAdapter {
|
|
666
|
+
return new MockTrezorAdapter({ ...config, deviceType: 'trezor' })
|
|
667
|
+
}
|