@meshconnect/uwc-core 1.0.3 → 1.1.0

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.
Files changed (121) hide show
  1. package/README.md +247 -76
  2. package/dist/events.d.ts +241 -5
  3. package/dist/events.d.ts.map +1 -1
  4. package/dist/events.js +77 -1
  5. package/dist/events.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/logger/create-logger.d.ts +32 -0
  11. package/dist/logger/create-logger.d.ts.map +1 -0
  12. package/dist/logger/create-logger.js +79 -0
  13. package/dist/logger/create-logger.js.map +1 -0
  14. package/dist/managers/event-manager.d.ts.map +1 -1
  15. package/dist/managers/event-manager.js +5 -1
  16. package/dist/managers/event-manager.js.map +1 -1
  17. package/dist/observability/connect-observer.d.ts +33 -0
  18. package/dist/observability/connect-observer.d.ts.map +1 -0
  19. package/dist/observability/connect-observer.js +115 -0
  20. package/dist/observability/connect-observer.js.map +1 -0
  21. package/dist/observability/focus-tracker.d.ts +42 -0
  22. package/dist/observability/focus-tracker.d.ts.map +1 -0
  23. package/dist/observability/focus-tracker.js +57 -0
  24. package/dist/observability/focus-tracker.js.map +1 -0
  25. package/dist/observability/instrument-handoff.d.ts +54 -0
  26. package/dist/observability/instrument-handoff.d.ts.map +1 -0
  27. package/dist/observability/instrument-handoff.js +173 -0
  28. package/dist/observability/instrument-handoff.js.map +1 -0
  29. package/dist/observability/lifecycle-bridge.d.ts +69 -0
  30. package/dist/observability/lifecycle-bridge.d.ts.map +1 -0
  31. package/dist/observability/lifecycle-bridge.js +155 -0
  32. package/dist/observability/lifecycle-bridge.js.map +1 -0
  33. package/dist/observability/scrub.d.ts +31 -0
  34. package/dist/observability/scrub.d.ts.map +1 -0
  35. package/dist/observability/scrub.js +167 -0
  36. package/dist/observability/scrub.js.map +1 -0
  37. package/dist/observability/telemetry.d.ts +157 -0
  38. package/dist/observability/telemetry.d.ts.map +1 -0
  39. package/dist/observability/telemetry.js +373 -0
  40. package/dist/observability/telemetry.js.map +1 -0
  41. package/dist/services/connection-service.d.ts +23 -1
  42. package/dist/services/connection-service.d.ts.map +1 -1
  43. package/dist/services/connection-service.js +128 -19
  44. package/dist/services/connection-service.js.map +1 -1
  45. package/dist/services/network-switch-service.d.ts +2 -1
  46. package/dist/services/network-switch-service.d.ts.map +1 -1
  47. package/dist/services/network-switch-service.js +45 -16
  48. package/dist/services/network-switch-service.js.map +1 -1
  49. package/dist/services/signature-service.d.ts +14 -7
  50. package/dist/services/signature-service.d.ts.map +1 -1
  51. package/dist/services/signature-service.js +56 -58
  52. package/dist/services/signature-service.js.map +1 -1
  53. package/dist/services/transaction-service.d.ts +19 -5
  54. package/dist/services/transaction-service.d.ts.map +1 -1
  55. package/dist/services/transaction-service.js +55 -38
  56. package/dist/services/transaction-service.js.map +1 -1
  57. package/dist/universal-wallet-connector.d.ts +56 -1
  58. package/dist/universal-wallet-connector.d.ts.map +1 -1
  59. package/dist/universal-wallet-connector.js +207 -110
  60. package/dist/universal-wallet-connector.js.map +1 -1
  61. package/dist/utils/id.d.ts +2 -0
  62. package/dist/utils/id.d.ts.map +1 -0
  63. package/dist/utils/id.js +41 -0
  64. package/dist/utils/id.js.map +1 -0
  65. package/dist/utils/resolve-session-provider.d.ts +10 -0
  66. package/dist/utils/resolve-session-provider.d.ts.map +1 -0
  67. package/dist/utils/resolve-session-provider.js +21 -0
  68. package/dist/utils/resolve-session-provider.js.map +1 -0
  69. package/dist/utils/to-wallet-error.d.ts +35 -0
  70. package/dist/utils/to-wallet-error.d.ts.map +1 -0
  71. package/dist/utils/to-wallet-error.js +130 -0
  72. package/dist/utils/to-wallet-error.js.map +1 -0
  73. package/dist/utils/validate-wallet-session.d.ts +15 -0
  74. package/dist/utils/validate-wallet-session.d.ts.map +1 -0
  75. package/dist/utils/validate-wallet-session.js +29 -0
  76. package/dist/utils/validate-wallet-session.js.map +1 -0
  77. package/dist/version.d.ts +2 -0
  78. package/dist/version.d.ts.map +1 -0
  79. package/dist/version.js +3 -0
  80. package/dist/version.js.map +1 -0
  81. package/package.json +6 -6
  82. package/src/events.ts +326 -14
  83. package/src/index.ts +10 -0
  84. package/src/logger/create-logger.test.ts +126 -0
  85. package/src/logger/create-logger.ts +112 -0
  86. package/src/managers/event-manager.test.ts +55 -0
  87. package/src/managers/event-manager.ts +5 -1
  88. package/src/observability/connect-observer.test.ts +214 -0
  89. package/src/observability/connect-observer.ts +128 -0
  90. package/src/observability/focus-tracker.test.ts +191 -0
  91. package/src/observability/focus-tracker.ts +96 -0
  92. package/src/observability/instrument-handoff.test.ts +397 -0
  93. package/src/observability/instrument-handoff.ts +231 -0
  94. package/src/observability/lifecycle-bridge.test.ts +398 -0
  95. package/src/observability/lifecycle-bridge.ts +216 -0
  96. package/src/observability/scrub.test.ts +183 -0
  97. package/src/observability/scrub.ts +174 -0
  98. package/src/observability/telemetry.test.ts +549 -0
  99. package/src/observability/telemetry.ts +546 -0
  100. package/src/services/connection-service.test.ts +261 -1
  101. package/src/services/connection-service.ts +167 -38
  102. package/src/services/network-switch-service.test.ts +51 -1
  103. package/src/services/network-switch-service.ts +56 -24
  104. package/src/services/signature-service.test.ts +181 -76
  105. package/src/services/signature-service.ts +75 -85
  106. package/src/services/transaction-service.test.ts +270 -69
  107. package/src/services/transaction-service.ts +74 -51
  108. package/src/universal-wallet-connector.instrumentation-guard.test.ts +199 -0
  109. package/src/universal-wallet-connector.observability.test.ts +610 -0
  110. package/src/universal-wallet-connector.test.ts +27 -146
  111. package/src/universal-wallet-connector.ts +279 -142
  112. package/src/utils/id.test.ts +15 -0
  113. package/src/utils/id.ts +48 -0
  114. package/src/utils/resolve-session-provider.test.ts +54 -0
  115. package/src/utils/resolve-session-provider.ts +25 -0
  116. package/src/utils/to-wallet-error.test.ts +181 -0
  117. package/src/utils/to-wallet-error.ts +152 -0
  118. package/src/utils/validate-wallet-session.test.ts +77 -0
  119. package/src/utils/validate-wallet-session.ts +40 -0
  120. package/src/version.test.ts +11 -0
  121. package/src/version.ts +2 -0
@@ -12,6 +12,7 @@ import type {
12
12
  import type { SessionManager } from '../managers/session-manager'
13
13
  import type { EventManager } from '../managers/event-manager'
14
14
  import type { ServiceOptions } from './connection-service'
15
+ import { instrumentHandoff } from '../observability/instrument-handoff'
15
16
 
16
17
  export class NetworkSwitchService {
17
18
  private isLoading = false
@@ -22,7 +23,9 @@ export class NetworkSwitchService {
22
23
  private sessionManager: SessionManager,
23
24
  private connectors: Map<ConnectionMode, Connector>,
24
25
  private usingIntegratedBrowser: boolean,
25
- private eventManager: EventManager
26
+ private eventManager: EventManager,
27
+ // Observational wallet-response deadline for the handoff timeout marker.
28
+ private walletResponseTimeoutMs: number
26
29
  ) {}
27
30
 
28
31
  getLoadingState() {
@@ -61,7 +64,13 @@ export class NetworkSwitchService {
61
64
  throw new Error('No connection mode found in session')
62
65
  }
63
66
 
64
- // Get the provider for the current connection mode
67
+ // Get the provider for the current connection mode.
68
+ // NOTE: deliberately NOT `resolveSessionProvider` (utils) — this
69
+ // pre-existing dispatch groups `tonConnect` with `injected` (a TON switch
70
+ // only proceeds when the wallet also carries injected-provider metadata,
71
+ // whose `supportedNetworkIds` gate below relies on), while the shared
72
+ // helper yields undefined for TON. Unifying them changes TON switch
73
+ // behaviour and needs its own change.
65
74
  let provider: SupportedProvider | undefined
66
75
 
67
76
  if (
@@ -106,6 +115,10 @@ export class NetworkSwitchService {
106
115
  session.activeNetwork
107
116
  )
108
117
 
118
+ // A no-op switch (target chain already active) still hands off to the wallet
119
+ // on some connectors; flag it so the funnel can separate it from real switches.
120
+ const targetAlreadyActive = networkId === session.activeNetwork?.id
121
+
109
122
  try {
110
123
  // Set loading states
111
124
  this.isLoading = true
@@ -115,28 +128,47 @@ export class NetworkSwitchService {
115
128
  isWaitingForUserApproval: requiresApproval
116
129
  })
117
130
 
118
- // Perform the network switch and get the new address
119
- // Pass the appropriate provider based on connection mode
120
- let result
121
- if (
122
- (session.connectionMode === 'injected' ||
123
- session.connectionMode === 'tonConnect') &&
124
- provider
125
- ) {
126
- result = await activeConnector.switchNetwork(
127
- network,
128
- provider as
129
- | ExtensionInjectedProvider
130
- | IntegratedBrowserInjectedProvider
131
- )
132
- } else if (session.connectionMode === 'walletConnect' && provider) {
133
- result = await activeConnector.switchNetwork(
134
- network,
135
- provider as WalletConnectProvider
136
- )
137
- } else {
138
- result = await activeConnector.switchNetwork(network)
139
- }
131
+ // Perform the network switch and get the new address. Instrument the
132
+ // wallet hand-off HERE after validation + the `networkSwitching` emit —
133
+ // so `awaitingWallet` marks the real wallet boundary; pre-wallet validation
134
+ // failures throw above and never enter the funnel.
135
+ // Pass the appropriate provider based on connection mode.
136
+ const result = await instrumentHandoff(
137
+ {
138
+ eventManager: this.eventManager,
139
+ timeoutMs: this.walletResponseTimeoutMs,
140
+ signal: options?.signal
141
+ },
142
+ {
143
+ operation: 'switchNetwork',
144
+ connectionMode: session.connectionMode,
145
+ walletId: currentWallet.id,
146
+ namespace: network.namespace,
147
+ chainId: network.id,
148
+ targetAlreadyActive
149
+ },
150
+ async () => {
151
+ if (
152
+ (session.connectionMode === 'injected' ||
153
+ session.connectionMode === 'tonConnect') &&
154
+ provider
155
+ ) {
156
+ return await activeConnector.switchNetwork(
157
+ network,
158
+ provider as
159
+ | ExtensionInjectedProvider
160
+ | IntegratedBrowserInjectedProvider
161
+ )
162
+ } else if (session.connectionMode === 'walletConnect' && provider) {
163
+ return await activeConnector.switchNetwork(
164
+ network,
165
+ provider as WalletConnectProvider
166
+ )
167
+ } else {
168
+ return await activeConnector.switchNetwork(network)
169
+ }
170
+ }
171
+ )
140
172
 
141
173
  options?.signal?.throwIfAborted()
142
174
 
@@ -1,16 +1,59 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest'
2
2
  import { SignatureService } from './signature-service'
3
3
  import { SessionManager } from '../managers/session-manager'
4
- import type { ConnectionMode, Connector } from '../../../types/src'
5
-
6
- describe('SignatureService', () => {
4
+ import { EventManager } from '../managers/event-manager'
5
+ import type {
6
+ ConnectionMode,
7
+ Connector,
8
+ WalletMetadata
9
+ } from '../../../types/src'
10
+
11
+ // The service owns the whole wallet-boundary op: validation (session guards,
12
+ // connector capability, provider resolution) BEFORE the funnel, then the
13
+ // instrumented connector call — same layering as ConnectionService /
14
+ // NetworkSwitchService. The façade only delegates + emits `error`.
15
+
16
+ const injectedWallet = {
17
+ id: 'metamask',
18
+ name: 'MetaMask',
19
+ extensionInjectedProvider: { supportedNetworkIds: ['eip155:1'] }
20
+ } as unknown as WalletMetadata
21
+
22
+ const walletWithoutProviders = {
23
+ id: 'metamask',
24
+ name: 'MetaMask'
25
+ } as unknown as WalletMetadata
26
+
27
+ describe('SignatureService.signMessage', () => {
7
28
  let signatureService: SignatureService
8
29
  let sessionManager: SessionManager
30
+ let eventManager: EventManager
9
31
  let mockConnector: Connector
10
32
  let connectors: Map<ConnectionMode, Connector>
33
+ let events: string[]
34
+
35
+ const connectSession = (
36
+ mode: ConnectionMode = 'injected',
37
+ wallet: WalletMetadata = injectedWallet
38
+ ) =>
39
+ sessionManager.updateSession({
40
+ connectionMode: mode,
41
+ activeWallet: wallet,
42
+ activeAddress: '0x123'
43
+ })
11
44
 
12
45
  beforeEach(() => {
13
46
  sessionManager = new SessionManager()
47
+ eventManager = new EventManager()
48
+ events = []
49
+ for (const name of [
50
+ 'awaitingWallet',
51
+ 'walletSucceeded',
52
+ 'walletRejected',
53
+ 'walletFailed'
54
+ ] as const) {
55
+ eventManager.on(name, () => events.push(name))
56
+ }
14
57
 
15
58
  mockConnector = {
16
59
  connect: vi.fn(),
@@ -23,41 +66,78 @@ describe('SignatureService', () => {
23
66
  connectors = new Map()
24
67
  connectors.set('injected', mockConnector)
25
68
 
26
- signatureService = new SignatureService(sessionManager, connectors)
27
- })
28
-
29
- it('should initialize with provided parameters', () => {
30
- expect(signatureService).toBeDefined()
69
+ signatureService = new SignatureService(
70
+ sessionManager,
71
+ connectors,
72
+ false,
73
+ eventManager,
74
+ 60_000
75
+ )
31
76
  })
32
77
 
33
- it('should throw error when no active connection', async () => {
78
+ it('throws "No active wallet" first on an empty session (public façade message)', async () => {
34
79
  await expect(signatureService.signMessage('test message')).rejects.toThrow(
35
- 'No active connection'
80
+ 'No active wallet'
36
81
  )
82
+ expect(events).toEqual([])
83
+ })
84
+
85
+ it('records walletSucceeded when an abort races completion (the wallet settled first)', async () => {
86
+ // The signature EXISTS — the funnel must record the wallet's truthful
87
+ // outcome; the abort still rejects the caller, after the terminal.
88
+ connectSession()
89
+ const controller = new AbortController()
90
+ vi.mocked(mockConnector.signMessage!).mockImplementation(async () => {
91
+ controller.abort()
92
+ return '0xsig' as never
93
+ })
94
+
95
+ await expect(
96
+ signatureService.signMessage('m', { signal: controller.signal })
97
+ ).rejects.toMatchObject({ name: 'AbortError' })
98
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
99
+ })
100
+
101
+ it('rejects with a CUSTOM abort reason after the wallet settled — success terminal, no spurious failure', async () => {
102
+ connectSession()
103
+ const controller = new AbortController()
104
+ vi.mocked(mockConnector.signMessage!).mockImplementation(async () => {
105
+ controller.abort(new Error('nav away'))
106
+ return '0xsig' as never
107
+ })
108
+
109
+ await expect(
110
+ signatureService.signMessage('m', { signal: controller.signal })
111
+ ).rejects.toThrow('nav away')
112
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
37
113
  })
38
114
 
39
- it('should throw error when no active address', async () => {
115
+ it('throws when no active address', async () => {
40
116
  sessionManager.updateSession({
41
- connectionMode: 'injected'
117
+ connectionMode: 'injected',
118
+ activeWallet: injectedWallet
42
119
  })
43
120
 
44
121
  await expect(signatureService.signMessage('test message')).rejects.toThrow(
45
122
  'No active wallet address'
46
123
  )
124
+ expect(events).toEqual([])
47
125
  })
48
126
 
49
- it('should throw error when connector not found', async () => {
127
+ it('throws when connector not found', async () => {
50
128
  sessionManager.updateSession({
51
129
  connectionMode: 'unknown' as ConnectionMode,
130
+ activeWallet: injectedWallet,
52
131
  activeAddress: '0x123'
53
132
  })
54
133
 
55
134
  await expect(signatureService.signMessage('test message')).rejects.toThrow(
56
135
  'No connector found for connection mode: unknown'
57
136
  )
137
+ expect(events).toEqual([])
58
138
  })
59
139
 
60
- it('should throw error when connector does not support signing', async () => {
140
+ it('throws when connector does not support signing', async () => {
61
141
  const connectorWithoutSign = {
62
142
  connect: vi.fn(),
63
143
  disconnect: vi.fn(),
@@ -65,53 +145,66 @@ describe('SignatureService', () => {
65
145
  switchNetwork: vi.fn()
66
146
  }
67
147
  connectors.set('injected', connectorWithoutSign)
68
-
69
- sessionManager.updateSession({
70
- connectionMode: 'injected',
71
- activeAddress: '0x123'
72
- })
148
+ connectSession()
73
149
 
74
150
  await expect(signatureService.signMessage('test message')).rejects.toThrow(
75
151
  'Connector does not support message signing'
76
152
  )
153
+ expect(events).toEqual([])
77
154
  })
78
155
 
79
- it('should sign message successfully', async () => {
80
- sessionManager.updateSession({
81
- connectionMode: 'injected',
82
- activeAddress: '0x123'
83
- })
156
+ it('a missing provider fails BEFORE the funnel (validation, not a wallet outcome)', async () => {
157
+ connectSession('injected', walletWithoutProviders)
84
158
 
85
- const provider = {} as any
86
- const result = await signatureService.signMessage('test message', provider)
87
- expect(result).toBe('0xsignature')
88
- expect(mockConnector.signMessage).toHaveBeenCalledWith(
89
- 'test message',
90
- provider
159
+ await expect(signatureService.signMessage('test message')).rejects.toThrow(
160
+ 'No provider available for the active wallet'
91
161
  )
162
+ expect(events).toEqual([])
163
+ expect(mockConnector.signMessage).not.toHaveBeenCalled()
92
164
  })
93
165
 
94
- it('should sign message with provider', async () => {
95
- sessionManager.updateSession({
96
- connectionMode: 'injected',
97
- activeAddress: '0x123'
98
- })
166
+ it('signs and emits awaitingWallet → walletSucceeded around the wallet call', async () => {
167
+ connectSession()
168
+
169
+ const result = await signatureService.signMessage('test message')
99
170
 
100
- const provider = {} as any
101
- const result = await signatureService.signMessage('test message', provider)
102
171
  expect(result).toBe('0xsignature')
172
+ // The resolved session provider is threaded to the connector.
103
173
  expect(mockConnector.signMessage).toHaveBeenCalledWith(
104
174
  'test message',
105
- provider
175
+ injectedWallet.extensionInjectedProvider
106
176
  )
177
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
178
+ })
179
+
180
+ it('a user rejection closes the handoff as walletRejected', async () => {
181
+ connectSession()
182
+ vi.mocked(mockConnector.signMessage!).mockRejectedValueOnce({ code: 4001 })
183
+
184
+ await expect(signatureService.signMessage('test message')).rejects.toEqual({
185
+ code: 4001
186
+ })
187
+ expect(events).toEqual(['awaitingWallet', 'walletRejected'])
188
+ })
189
+
190
+ it('tonConnect enters the funnel with the connector resolving its own provider', async () => {
191
+ connectors.set('tonConnect', mockConnector)
192
+ connectSession('tonConnect', walletWithoutProviders)
193
+
194
+ await signatureService.signMessage('hello')
195
+
196
+ expect(mockConnector.signMessage).toHaveBeenCalledWith('hello')
197
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
107
198
  })
108
199
  })
109
200
 
110
201
  describe('SignatureService.signTypedData', () => {
111
202
  let signatureService: SignatureService
112
203
  let sessionManager: SessionManager
204
+ let eventManager: EventManager
113
205
  let mockConnector: Connector
114
206
  let connectors: Map<ConnectionMode, Connector>
207
+ let events: string[]
115
208
 
116
209
  const typedData = {
117
210
  domain: {
@@ -143,6 +236,15 @@ describe('SignatureService.signTypedData', () => {
143
236
 
144
237
  beforeEach(() => {
145
238
  sessionManager = new SessionManager()
239
+ eventManager = new EventManager()
240
+ events = []
241
+ for (const name of [
242
+ 'awaitingWallet',
243
+ 'walletSucceeded',
244
+ 'walletFailed'
245
+ ] as const) {
246
+ eventManager.on(name, () => events.push(name))
247
+ }
146
248
 
147
249
  mockConnector = {
148
250
  connect: vi.fn(),
@@ -156,33 +258,23 @@ describe('SignatureService.signTypedData', () => {
156
258
  connectors = new Map()
157
259
  connectors.set('injected', mockConnector)
158
260
 
159
- signatureService = new SignatureService(sessionManager, connectors)
160
- })
161
-
162
- it('should throw when no active connection', async () => {
163
- await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
164
- 'No active connection'
261
+ signatureService = new SignatureService(
262
+ sessionManager,
263
+ connectors,
264
+ false,
265
+ eventManager,
266
+ 60_000
165
267
  )
166
268
  })
167
269
 
168
- it('should throw when no active address', async () => {
169
- sessionManager.updateSession({ connectionMode: 'injected' })
170
- await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
171
- 'No active wallet address'
172
- )
173
- })
174
-
175
- it('should throw when connector not found', async () => {
176
- sessionManager.updateSession({
177
- connectionMode: 'unknown' as ConnectionMode,
178
- activeAddress: '0x123'
179
- })
270
+ it('throws "No active wallet" first on an empty session', async () => {
180
271
  await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
181
- 'No connector found for connection mode: unknown'
272
+ 'No active wallet'
182
273
  )
274
+ expect(events).toEqual([])
183
275
  })
184
276
 
185
- it('should throw when connector does not support signTypedData', async () => {
277
+ it('throws when connector does not support signTypedData', async () => {
186
278
  const connectorWithoutTypedData = {
187
279
  connect: vi.fn(),
188
280
  disconnect: vi.fn(),
@@ -192,56 +284,69 @@ describe('SignatureService.signTypedData', () => {
192
284
  connectors.set('injected', connectorWithoutTypedData)
193
285
  sessionManager.updateSession({
194
286
  connectionMode: 'injected',
287
+ activeWallet: injectedWallet,
195
288
  activeAddress: '0x123'
196
289
  })
197
290
  await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
198
291
  'Connector does not support typed data signing'
199
292
  )
293
+ expect(events).toEqual([])
294
+ })
295
+
296
+ it('throws the façade message for TON, outside the funnel', async () => {
297
+ // Shape of the real TonConnectConnector: NO signTypedData. The TON branch
298
+ // must win over the generic capability guard, not be shadowed by it.
299
+ const tonConnector = { ...mockConnector }
300
+ delete (tonConnector as { signTypedData?: unknown }).signTypedData
301
+ connectors.set('tonConnect', tonConnector)
302
+ sessionManager.updateSession({
303
+ connectionMode: 'tonConnect',
304
+ activeWallet: injectedWallet,
305
+ activeAddress: '0x123'
306
+ })
307
+ await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
308
+ 'signTypedData is not supported for TON wallets'
309
+ )
310
+ expect(events).toEqual([])
200
311
  })
201
312
 
202
- it('should throw when injected mode has no provider', async () => {
313
+ it('a missing injected provider fails BEFORE the funnel', async () => {
203
314
  sessionManager.updateSession({
204
315
  connectionMode: 'injected',
316
+ activeWallet: walletWithoutProviders,
205
317
  activeAddress: '0x123'
206
318
  })
207
319
  await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
208
- 'Provider is required for injected connector'
320
+ 'No provider available for the active wallet'
209
321
  )
322
+ expect(events).toEqual([])
210
323
  })
211
324
 
212
- it('should sign typed data via injected connector', async () => {
325
+ it('signs via the injected connector inside the funnel', async () => {
213
326
  sessionManager.updateSession({
214
327
  connectionMode: 'injected',
328
+ activeWallet: injectedWallet,
215
329
  activeAddress: '0x123'
216
330
  })
217
- const provider = {} as any
218
- const result = await signatureService.signTypedData(typedData, provider)
331
+ const result = await signatureService.signTypedData(typedData)
219
332
  expect(result).toBe('0xtypedsig')
220
333
  expect(mockConnector.signTypedData).toHaveBeenCalledWith(
221
334
  typedData,
222
- provider
335
+ injectedWallet.extensionInjectedProvider
223
336
  )
337
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
224
338
  })
225
339
 
226
- it('should sign typed data via walletConnect connector without provider', async () => {
340
+ it('signs via walletConnect with no provider (connector resolves its own)', async () => {
227
341
  connectors.set('walletConnect', mockConnector)
228
342
  sessionManager.updateSession({
229
343
  connectionMode: 'walletConnect',
344
+ activeWallet: walletWithoutProviders,
230
345
  activeAddress: '0x123'
231
346
  })
232
347
  const result = await signatureService.signTypedData(typedData)
233
348
  expect(result).toBe('0xtypedsig')
234
349
  expect(mockConnector.signTypedData).toHaveBeenCalledWith(typedData)
235
- })
236
-
237
- it('should throw for tonConnect mode', async () => {
238
- connectors.set('tonConnect', mockConnector)
239
- sessionManager.updateSession({
240
- connectionMode: 'tonConnect',
241
- activeAddress: '0x123'
242
- })
243
- await expect(signatureService.signTypedData(typedData)).rejects.toThrow(
244
- 'signTypedData is not supported for connection mode: tonConnect'
245
- )
350
+ expect(events).toEqual(['awaitingWallet', 'walletSucceeded'])
246
351
  })
247
352
  })