@meshconnect/uwc-core 1.9.0 → 1.10.0-snapshot.0112a96
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/events.d.ts +107 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +4 -0
- package/dist/events.js.map +1 -1
- package/dist/observability/instrument-handoff.d.ts +21 -3
- package/dist/observability/instrument-handoff.d.ts.map +1 -1
- package/dist/observability/instrument-handoff.js +38 -4
- package/dist/observability/instrument-handoff.js.map +1 -1
- package/dist/observability/telemetry.d.ts +39 -9
- package/dist/observability/telemetry.d.ts.map +1 -1
- package/dist/observability/telemetry.js +53 -5
- package/dist/observability/telemetry.js.map +1 -1
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +39 -1
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/services/signature-service.d.ts.map +1 -1
- package/dist/services/signature-service.js +10 -3
- package/dist/services/signature-service.js.map +1 -1
- package/dist/services/transaction-service.d.ts.map +1 -1
- package/dist/services/transaction-service.js +11 -3
- package/dist/services/transaction-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +3 -0
- package/dist/universal-wallet-connector.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/events.ts +118 -0
- package/src/observability/instrument-handoff.ts +52 -5
- package/src/observability/telemetry.test.ts +111 -0
- package/src/observability/telemetry.ts +104 -14
- package/src/services/network-switch-service.ts +48 -1
- package/src/services/signature-service.ts +18 -3
- package/src/services/transaction-service.ts +19 -3
- package/src/universal-wallet-connector.ts +4 -0
- package/src/version.ts +1 -1
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
Session
|
|
6
6
|
} from '@meshconnect/uwc-types'
|
|
7
7
|
import type { EventManager } from '../managers/event-manager'
|
|
8
|
-
import type { UWCOperation } from '../events'
|
|
8
|
+
import type { ProviderKind, UWCOperation } from '../events'
|
|
9
9
|
import { generateId } from '../utils/id'
|
|
10
10
|
import {
|
|
11
11
|
classifyError,
|
|
@@ -26,19 +26,56 @@ export interface HandoffContext {
|
|
|
26
26
|
chainId?: NetworkId | undefined
|
|
27
27
|
/** switchNetwork only: target chain already the active one (no-op switch). */
|
|
28
28
|
targetAlreadyActive?: boolean | undefined
|
|
29
|
+
/** Provider surface used (namespace-parking risk is integratedBrowser-only). */
|
|
30
|
+
providerKind?: ProviderKind | undefined
|
|
31
|
+
/** Session-level: running inside the wallet's own in-app browser. */
|
|
32
|
+
usingIntegratedBrowser?: boolean | undefined
|
|
33
|
+
/** Active wallet's legacy `solana.injectedId` (e.g. coinbaseSolana), when set. */
|
|
34
|
+
injectedId?: string | undefined
|
|
35
|
+
/** Last namespace actually signed/sent before this op — the parking proxy. */
|
|
36
|
+
lastSignedNamespace?: Namespace | undefined
|
|
37
|
+
/** switchNetwork only: namespace active BEFORE the switch (cross-namespace detection). */
|
|
38
|
+
previousNamespace?: Namespace | undefined
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
/**
|
|
41
|
+
/** Which provider surface a namespace/mode maps to (parking risk = integratedBrowser). */
|
|
42
|
+
export function deriveProviderKind(
|
|
43
|
+
connectionMode: ConnectionMode,
|
|
44
|
+
usingIntegratedBrowser: boolean
|
|
45
|
+
): ProviderKind {
|
|
46
|
+
if (connectionMode === 'walletConnect') return 'walletConnect'
|
|
47
|
+
if (connectionMode === 'tonConnect') return 'tonConnect'
|
|
48
|
+
return usingIntegratedBrowser ? 'integratedBrowser' : 'extension'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build handoff routing facets from the active session (no address). Enriched
|
|
53
|
+
* with the namespace-parking triage facets — provider surface, in-app-browser,
|
|
54
|
+
* the wallet's legacy `solana.injectedId`, and the last-signed namespace — so a
|
|
55
|
+
* send/sign handoff self-describes the parking setup without a session join.
|
|
56
|
+
* `usingIntegratedBrowser` is connector-level state, so callers pass it in.
|
|
57
|
+
*/
|
|
32
58
|
export function sessionHandoffContext(
|
|
33
59
|
operation: UWCOperation,
|
|
34
|
-
session: Session
|
|
60
|
+
session: Session,
|
|
61
|
+
usingIntegratedBrowser = false
|
|
35
62
|
): HandoffContext {
|
|
63
|
+
const connectionMode = session.connectionMode ?? 'injected'
|
|
64
|
+
// `namespaceMetaData` (with the legacy `solana.injectedId`) lives on the
|
|
65
|
+
// injected provider, not the wallet — pick the surface in use.
|
|
66
|
+
const injectedProvider = usingIntegratedBrowser
|
|
67
|
+
? session.activeWallet?.integratedBrowserInjectedProvider
|
|
68
|
+
: session.activeWallet?.extensionInjectedProvider
|
|
36
69
|
return {
|
|
37
70
|
operation,
|
|
38
|
-
connectionMode
|
|
71
|
+
connectionMode,
|
|
39
72
|
walletId: session.activeWallet?.id,
|
|
40
73
|
namespace: session.activeNetwork?.namespace,
|
|
41
|
-
chainId: session.activeNetwork?.id ?? undefined
|
|
74
|
+
chainId: session.activeNetwork?.id ?? undefined,
|
|
75
|
+
providerKind: deriveProviderKind(connectionMode, usingIntegratedBrowser),
|
|
76
|
+
usingIntegratedBrowser,
|
|
77
|
+
injectedId: injectedProvider?.namespaceMetaData?.solana?.injectedId,
|
|
78
|
+
lastSignedNamespace: session.lastSignedNamespace ?? undefined
|
|
42
79
|
}
|
|
43
80
|
}
|
|
44
81
|
|
|
@@ -134,6 +171,11 @@ export async function instrumentHandoff<T>(
|
|
|
134
171
|
handoffId,
|
|
135
172
|
walletFlowType: deriveWalletFlowType(context.connectionMode, platform),
|
|
136
173
|
platform,
|
|
174
|
+
providerKind: context.providerKind,
|
|
175
|
+
usingIntegratedBrowser: context.usingIntegratedBrowser,
|
|
176
|
+
injectedId: context.injectedId,
|
|
177
|
+
lastSignedNamespace: context.lastSignedNamespace,
|
|
178
|
+
previousNamespace: context.previousNamespace,
|
|
137
179
|
...(context.targetAlreadyActive !== undefined && {
|
|
138
180
|
targetAlreadyActive: context.targetAlreadyActive
|
|
139
181
|
})
|
|
@@ -176,6 +218,11 @@ export async function instrumentHandoff<T>(
|
|
|
176
218
|
walletId: context.walletId,
|
|
177
219
|
namespace: context.namespace,
|
|
178
220
|
chainId: context.chainId,
|
|
221
|
+
providerKind: context.providerKind,
|
|
222
|
+
usingIntegratedBrowser: context.usingIntegratedBrowser,
|
|
223
|
+
injectedId: context.injectedId,
|
|
224
|
+
lastSignedNamespace: context.lastSignedNamespace,
|
|
225
|
+
previousNamespace: context.previousNamespace,
|
|
179
226
|
...(context.targetAlreadyActive !== undefined && {
|
|
180
227
|
targetAlreadyActive: context.targetAlreadyActive
|
|
181
228
|
})
|
|
@@ -546,6 +546,41 @@ describe('normalizeEvent', () => {
|
|
|
546
546
|
expect(capped.runtime).toBe('iframe')
|
|
547
547
|
})
|
|
548
548
|
|
|
549
|
+
it('carries injectedId + gatherSource + runtime for secondarySolanaConnectGathered, length-capped (ONC-231)', () => {
|
|
550
|
+
const out = normalizeEvent(
|
|
551
|
+
'secondarySolanaConnectGathered',
|
|
552
|
+
{
|
|
553
|
+
injectedId: 'coinbaseSolana',
|
|
554
|
+
source: 'connect',
|
|
555
|
+
legacyInjected: true,
|
|
556
|
+
framed: true
|
|
557
|
+
},
|
|
558
|
+
SDK_SESSION,
|
|
559
|
+
NOW
|
|
560
|
+
)
|
|
561
|
+
expect(out.injectedId).toBe('coinbaseSolana')
|
|
562
|
+
expect(out.legacyInjected).toBe(true)
|
|
563
|
+
expect(out.gatherSource).toBe('connect')
|
|
564
|
+
expect(out.runtime).toBe('iframe')
|
|
565
|
+
// never leaks a gateOutcome (that's the skip event's field)
|
|
566
|
+
expect(out.gateOutcome).toBeUndefined()
|
|
567
|
+
|
|
568
|
+
const capped = normalizeEvent(
|
|
569
|
+
'secondarySolanaConnectGathered',
|
|
570
|
+
{
|
|
571
|
+
injectedId: 'y'.repeat(200),
|
|
572
|
+
source: 'existing',
|
|
573
|
+
legacyInjected: false,
|
|
574
|
+
framed: false
|
|
575
|
+
},
|
|
576
|
+
SDK_SESSION,
|
|
577
|
+
NOW
|
|
578
|
+
)
|
|
579
|
+
expect(capped.injectedId).toHaveLength(64)
|
|
580
|
+
expect(capped.gatherSource).toBe('existing')
|
|
581
|
+
expect(capped.runtime).toBe('direct')
|
|
582
|
+
})
|
|
583
|
+
|
|
549
584
|
it('normalizes lifecycle events with routing only, no address (P2)', () => {
|
|
550
585
|
const acct = normalizeEvent(
|
|
551
586
|
'walletAccountChanged',
|
|
@@ -697,4 +732,80 @@ describe('normalizeEvent', () => {
|
|
|
697
732
|
)
|
|
698
733
|
expect('handoffCovered' in out).toBe(false)
|
|
699
734
|
})
|
|
735
|
+
|
|
736
|
+
it('normalizes namespaceReassert(succeeded), mapping every field onto its reassert* facet', () => {
|
|
737
|
+
const out = normalizeEvent(
|
|
738
|
+
'namespaceReassert',
|
|
739
|
+
{
|
|
740
|
+
phase: 'succeeded',
|
|
741
|
+
lever: 'eth_requestAccounts',
|
|
742
|
+
connectionMode: 'injected',
|
|
743
|
+
walletId: 'coinbase',
|
|
744
|
+
injectedId: 'coinbaseSolana',
|
|
745
|
+
fromNamespace: 'solana',
|
|
746
|
+
toNamespace: 'eip155',
|
|
747
|
+
durationMs: 42
|
|
748
|
+
},
|
|
749
|
+
SDK_SESSION,
|
|
750
|
+
NOW
|
|
751
|
+
)
|
|
752
|
+
expect(out.reassertPhase).toBe('succeeded')
|
|
753
|
+
expect(out.reassertLever).toBe('eth_requestAccounts')
|
|
754
|
+
expect(out.connectionMode).toBe('injected')
|
|
755
|
+
expect(out.walletId).toBe('coinbase')
|
|
756
|
+
expect(out.injectedId).toBe('coinbaseSolana')
|
|
757
|
+
expect(out.fromNamespace).toBe('solana')
|
|
758
|
+
expect(out.toNamespace).toBe('eip155')
|
|
759
|
+
expect(out.durationMs).toBe(42)
|
|
760
|
+
expect(out.error).toBeUndefined()
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
it('normalizes namespaceReassert(failed), capping injectedId and scrubbing the error message', () => {
|
|
764
|
+
const addr = '0x' + 'a'.repeat(40)
|
|
765
|
+
const out = normalizeEvent(
|
|
766
|
+
'namespaceReassert',
|
|
767
|
+
{
|
|
768
|
+
phase: 'failed',
|
|
769
|
+
lever: 'eth_requestAccounts',
|
|
770
|
+
connectionMode: 'injected',
|
|
771
|
+
walletId: 'coinbase',
|
|
772
|
+
injectedId: 'x'.repeat(200),
|
|
773
|
+
fromNamespace: 'solana',
|
|
774
|
+
toNamespace: 'eip155',
|
|
775
|
+
durationMs: 12,
|
|
776
|
+
error: { type: 'unknown', message: `declined for ${addr}` }
|
|
777
|
+
},
|
|
778
|
+
SDK_SESSION,
|
|
779
|
+
NOW
|
|
780
|
+
)
|
|
781
|
+
expect(out.reassertPhase).toBe('failed')
|
|
782
|
+
expect(out.injectedId).toHaveLength(64)
|
|
783
|
+
expect(out.error?.message).not.toContain(addr)
|
|
784
|
+
expect(out.error?.message).toContain('[redacted-hex]')
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
it('carries parkingFacets (providerKind, usingIntegratedBrowser, injectedId capped, lastSignedNamespace, previousNamespace) on a handoff terminal', () => {
|
|
788
|
+
const out = normalizeEvent(
|
|
789
|
+
'walletTimedOut',
|
|
790
|
+
{
|
|
791
|
+
handoffId: 'h-1',
|
|
792
|
+
operation: 'sendTransaction',
|
|
793
|
+
durationMs: 1000,
|
|
794
|
+
connectionMode: 'injected',
|
|
795
|
+
timeoutMs: 1000,
|
|
796
|
+
providerKind: 'integratedBrowser',
|
|
797
|
+
usingIntegratedBrowser: true,
|
|
798
|
+
injectedId: 'y'.repeat(200),
|
|
799
|
+
lastSignedNamespace: 'solana',
|
|
800
|
+
previousNamespace: 'solana'
|
|
801
|
+
},
|
|
802
|
+
SDK_SESSION,
|
|
803
|
+
NOW
|
|
804
|
+
)
|
|
805
|
+
expect(out.providerKind).toBe('integratedBrowser')
|
|
806
|
+
expect(out.usingIntegratedBrowser).toBe(true)
|
|
807
|
+
expect(out.injectedId).toHaveLength(64)
|
|
808
|
+
expect(out.lastSignedNamespace).toBe('solana')
|
|
809
|
+
expect(out.previousNamespace).toBe('solana')
|
|
810
|
+
})
|
|
700
811
|
})
|
|
@@ -11,7 +11,12 @@ import type {
|
|
|
11
11
|
LifecycleUnavailableReason,
|
|
12
12
|
Session
|
|
13
13
|
} from '@meshconnect/uwc-types'
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
UWCEventName,
|
|
16
|
+
UWCEventMap,
|
|
17
|
+
UWCOperation,
|
|
18
|
+
ProviderKind
|
|
19
|
+
} from '../events'
|
|
15
20
|
import { UWC_CORE_VERSION } from '../version'
|
|
16
21
|
import { scrubMessage } from './scrub'
|
|
17
22
|
|
|
@@ -50,6 +55,29 @@ function capNetworkMismatch(
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Namespace-parking triage facets carried on every wallet-boundary handoff so a
|
|
60
|
+
* single event answers "which provider surface, in-app browser?, legacy Solana
|
|
61
|
+
* wallet?, and what namespace did we last sign on before this op?" — the inputs
|
|
62
|
+
* needed to spot the Coinbase/Base sequential Solana→EVM parking bug in RUM.
|
|
63
|
+
* `injectedId` is length-capped (catalog-metadata origin), the rest are enums/bools.
|
|
64
|
+
*/
|
|
65
|
+
function parkingFacets(d: {
|
|
66
|
+
providerKind?: ProviderKind | undefined
|
|
67
|
+
usingIntegratedBrowser?: boolean | undefined
|
|
68
|
+
injectedId?: string | undefined
|
|
69
|
+
lastSignedNamespace?: Namespace | undefined
|
|
70
|
+
previousNamespace?: Namespace | undefined
|
|
71
|
+
}) {
|
|
72
|
+
return {
|
|
73
|
+
providerKind: d.providerKind,
|
|
74
|
+
usingIntegratedBrowser: d.usingIntegratedBrowser,
|
|
75
|
+
injectedId: d.injectedId?.slice(0, MAX_INJECTED_ID_LENGTH),
|
|
76
|
+
lastSignedNamespace: d.lastSignedNamespace,
|
|
77
|
+
previousNamespace: d.previousNamespace
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
/**
|
|
54
82
|
* A normalized, PII-safe telemetry record derived from a `UWCEventMap` event.
|
|
55
83
|
*
|
|
@@ -193,17 +221,17 @@ export interface UWCTelemetryEvent {
|
|
|
193
221
|
/** Handoff terminals, WalletConnect only: relay-socket drops seen while the handoff was pending. */
|
|
194
222
|
relayDropCount?: number | undefined
|
|
195
223
|
/**
|
|
196
|
-
* `secondarySolanaConnectSkipped`
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
224
|
+
* `secondarySolanaConnectSkipped` / `secondarySolanaConnectGathered`: the
|
|
225
|
+
* `solana.injectedId` metadata key the secondary gather acted on (e.g.
|
|
226
|
+
* `coinbaseSolana`). A consumer-configured metadata key, never an address.
|
|
227
|
+
* Length-capped as defense-in-depth since the value originates in catalog
|
|
228
|
+
* metadata.
|
|
201
229
|
*/
|
|
202
230
|
injectedId?: string | undefined
|
|
203
231
|
/**
|
|
204
|
-
* `secondarySolanaConnectSkipped`
|
|
205
|
-
* discovered via the legacy injected-Solana fallback
|
|
206
|
-
* rather than the Wallet Standard registry.
|
|
232
|
+
* `secondarySolanaConnectSkipped` / `secondarySolanaConnectGathered`: true
|
|
233
|
+
* when the wallet was discovered via the legacy injected-Solana fallback
|
|
234
|
+
* (true "Legacy mode") rather than the Wallet Standard registry.
|
|
207
235
|
*/
|
|
208
236
|
legacyInjected?: boolean | undefined
|
|
209
237
|
/**
|
|
@@ -213,6 +241,36 @@ export interface UWCTelemetryEvent {
|
|
|
213
241
|
* wallet-side state, not the gather.
|
|
214
242
|
*/
|
|
215
243
|
gateOutcome?: 'connectSuppressed' | 'alreadyConnected' | undefined
|
|
244
|
+
/**
|
|
245
|
+
* `secondarySolanaConnectGathered` only (ONC-231): `existing` = the address
|
|
246
|
+
* surfaced via the passive/silent check; `connect` = a fresh connect was made.
|
|
247
|
+
*/
|
|
248
|
+
gatherSource?: 'existing' | 'connect' | undefined
|
|
249
|
+
/**
|
|
250
|
+
* Handoff events: which provider surface the op used. Namespace parking
|
|
251
|
+
* (Coinbase/Base legacy Solana → EVM in-app) is observed only on
|
|
252
|
+
* `integratedBrowser`; riding it on every handoff separates that surface.
|
|
253
|
+
*/
|
|
254
|
+
providerKind?: ProviderKind | undefined
|
|
255
|
+
/** Handoff events: the session runs inside the wallet's own in-app browser. */
|
|
256
|
+
usingIntegratedBrowser?: boolean | undefined
|
|
257
|
+
/**
|
|
258
|
+
* Handoff events: the last namespace actually signed/sent before this op — the
|
|
259
|
+
* parking proxy. A switch/send to `eip155` following `lastSignedNamespace:
|
|
260
|
+
* solana` on a legacy-injectedId wallet in an integrated browser is the
|
|
261
|
+
* fingerprint of the no-approval-sheet parking bug.
|
|
262
|
+
*/
|
|
263
|
+
lastSignedNamespace?: Namespace | undefined
|
|
264
|
+
/** switchNetwork handoffs: the namespace active BEFORE the switch. */
|
|
265
|
+
previousNamespace?: Namespace | undefined
|
|
266
|
+
/** `namespaceReassert` only: lever attempt phase. */
|
|
267
|
+
reassertPhase?: 'fired' | 'succeeded' | 'failed' | undefined
|
|
268
|
+
/** `namespaceReassert` only: the wallet request used to re-assert the namespace. */
|
|
269
|
+
reassertLever?: 'eth_requestAccounts' | undefined
|
|
270
|
+
/** `namespaceReassert` only: the namespace the wallet was parked on (parking cause). */
|
|
271
|
+
fromNamespace?: Namespace | undefined
|
|
272
|
+
/** `namespaceReassert` only: the namespace being re-asserted (switch target). */
|
|
273
|
+
toNamespace?: Namespace | undefined
|
|
216
274
|
}
|
|
217
275
|
|
|
218
276
|
/**
|
|
@@ -405,7 +463,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
405
463
|
handoffId: d.handoffId,
|
|
406
464
|
walletFlowType: d.walletFlowType,
|
|
407
465
|
platform: d.platform,
|
|
408
|
-
targetAlreadyActive: d.targetAlreadyActive
|
|
466
|
+
targetAlreadyActive: d.targetAlreadyActive,
|
|
467
|
+
...parkingFacets(d)
|
|
409
468
|
}
|
|
410
469
|
}
|
|
411
470
|
case 'walletSucceeded': {
|
|
@@ -424,7 +483,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
424
483
|
targetAlreadyActive: d.targetAlreadyActive,
|
|
425
484
|
backgroundedMs: d.backgroundedMs,
|
|
426
485
|
backgroundCount: d.backgroundCount,
|
|
427
|
-
relayDropCount: d.relayDropCount
|
|
486
|
+
relayDropCount: d.relayDropCount,
|
|
487
|
+
...parkingFacets(d)
|
|
428
488
|
}
|
|
429
489
|
}
|
|
430
490
|
case 'walletRejected': {
|
|
@@ -449,7 +509,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
449
509
|
: undefined,
|
|
450
510
|
backgroundedMs: d.backgroundedMs,
|
|
451
511
|
backgroundCount: d.backgroundCount,
|
|
452
|
-
relayDropCount: d.relayDropCount
|
|
512
|
+
relayDropCount: d.relayDropCount,
|
|
513
|
+
...parkingFacets(d)
|
|
453
514
|
}
|
|
454
515
|
}
|
|
455
516
|
case 'walletTimedOut': {
|
|
@@ -467,7 +528,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
467
528
|
targetAlreadyActive: d.targetAlreadyActive,
|
|
468
529
|
backgroundedMs: d.backgroundedMs,
|
|
469
530
|
backgroundCount: d.backgroundCount,
|
|
470
|
-
relayDropCount: d.relayDropCount
|
|
531
|
+
relayDropCount: d.relayDropCount,
|
|
532
|
+
...parkingFacets(d)
|
|
471
533
|
}
|
|
472
534
|
}
|
|
473
535
|
case 'walletFailed': {
|
|
@@ -490,7 +552,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
490
552
|
error: { type: d.error.type, message: scrubMessage(d.error.message) },
|
|
491
553
|
backgroundedMs: d.backgroundedMs,
|
|
492
554
|
backgroundCount: d.backgroundCount,
|
|
493
|
-
relayDropCount: d.relayDropCount
|
|
555
|
+
relayDropCount: d.relayDropCount,
|
|
556
|
+
...parkingFacets(d)
|
|
494
557
|
}
|
|
495
558
|
}
|
|
496
559
|
case 'capabilitiesUpdated': {
|
|
@@ -539,6 +602,33 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
539
602
|
runtime: d.framed ? 'iframe' : 'direct'
|
|
540
603
|
}
|
|
541
604
|
}
|
|
605
|
+
case 'secondarySolanaConnectGathered': {
|
|
606
|
+
const d = data as UWCEventMap['secondarySolanaConnectGathered']
|
|
607
|
+
return {
|
|
608
|
+
...base,
|
|
609
|
+
injectedId: d.injectedId.slice(0, MAX_INJECTED_ID_LENGTH),
|
|
610
|
+
legacyInjected: d.legacyInjected,
|
|
611
|
+
gatherSource: d.source,
|
|
612
|
+
runtime: d.framed ? 'iframe' : 'direct'
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
case 'namespaceReassert': {
|
|
616
|
+
const d = data as UWCEventMap['namespaceReassert']
|
|
617
|
+
return {
|
|
618
|
+
...base,
|
|
619
|
+
reassertPhase: d.phase,
|
|
620
|
+
reassertLever: d.lever,
|
|
621
|
+
connectionMode: d.connectionMode,
|
|
622
|
+
walletId: d.walletId,
|
|
623
|
+
injectedId: d.injectedId?.slice(0, MAX_INJECTED_ID_LENGTH),
|
|
624
|
+
fromNamespace: d.fromNamespace,
|
|
625
|
+
toNamespace: d.toNamespace,
|
|
626
|
+
durationMs: d.durationMs,
|
|
627
|
+
error: d.error
|
|
628
|
+
? { type: d.error.type, message: scrubMessage(d.error.message) }
|
|
629
|
+
: undefined
|
|
630
|
+
}
|
|
631
|
+
}
|
|
542
632
|
case 'pageHiddenDuringHandoff':
|
|
543
633
|
case 'pageVisibleDuringHandoff': {
|
|
544
634
|
// Cast to the visible shape (superset). `hiddenMs` is absent on the hidden
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from '@meshconnect/uwc-types'
|
|
12
12
|
import type { SessionManager } from '../managers/session-manager'
|
|
13
13
|
import type { EventManager } from '../managers/event-manager'
|
|
14
|
+
import type { ProviderKind } from '../events'
|
|
14
15
|
import type { ServiceOptions } from './connection-service'
|
|
15
16
|
import { instrumentHandoff } from '../observability/instrument-handoff'
|
|
16
17
|
|
|
@@ -97,6 +98,30 @@ export class NetworkSwitchService {
|
|
|
97
98
|
)
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// Namespace-parking triage facets (rides the switch handoff). The parking bug
|
|
102
|
+
// (Coinbase/Base legacy Solana leaves the wallet's active namespace on Solana
|
|
103
|
+
// so the next EVM send shows no approval sheet) only occurs on the
|
|
104
|
+
// integratedBrowser surface for a wallet that declares a `solana.injectedId`.
|
|
105
|
+
const providerKind: ProviderKind =
|
|
106
|
+
session.connectionMode === 'walletConnect'
|
|
107
|
+
? 'walletConnect'
|
|
108
|
+
: session.connectionMode === 'tonConnect'
|
|
109
|
+
? 'tonConnect'
|
|
110
|
+
: this.usingIntegratedBrowser
|
|
111
|
+
? 'integratedBrowser'
|
|
112
|
+
: 'extension'
|
|
113
|
+
const solanaInjectedId =
|
|
114
|
+
session.connectionMode === 'injected' ||
|
|
115
|
+
session.connectionMode === 'tonConnect'
|
|
116
|
+
? (
|
|
117
|
+
provider as
|
|
118
|
+
| ExtensionInjectedProvider
|
|
119
|
+
| IntegratedBrowserInjectedProvider
|
|
120
|
+
).namespaceMetaData?.solana?.injectedId
|
|
121
|
+
: undefined
|
|
122
|
+
// Captured BEFORE the switch mutates the session — the cross-namespace signal.
|
|
123
|
+
const previousNamespace = session.activeNetwork?.namespace
|
|
124
|
+
|
|
100
125
|
// Get the active connector
|
|
101
126
|
const activeConnector = this.connectors.get(session.connectionMode)
|
|
102
127
|
|
|
@@ -145,7 +170,11 @@ export class NetworkSwitchService {
|
|
|
145
170
|
walletId: currentWallet.id,
|
|
146
171
|
namespace: network.namespace,
|
|
147
172
|
chainId: network.id,
|
|
148
|
-
targetAlreadyActive
|
|
173
|
+
targetAlreadyActive,
|
|
174
|
+
providerKind,
|
|
175
|
+
usingIntegratedBrowser: this.usingIntegratedBrowser,
|
|
176
|
+
injectedId: solanaInjectedId,
|
|
177
|
+
previousNamespace
|
|
149
178
|
},
|
|
150
179
|
async () => {
|
|
151
180
|
if (
|
|
@@ -172,6 +201,24 @@ export class NetworkSwitchService {
|
|
|
172
201
|
|
|
173
202
|
options?.signal?.throwIfAborted()
|
|
174
203
|
|
|
204
|
+
// Surface the POC un-park lever (if the injected connector fired it) as a
|
|
205
|
+
// queryable telemetry event — the "did re-assert fix the parking?" signal.
|
|
206
|
+
if (result.reassert?.fired) {
|
|
207
|
+
this.eventManager.emit('namespaceReassert', {
|
|
208
|
+
phase: result.reassert.ok ? 'succeeded' : 'failed',
|
|
209
|
+
lever: result.reassert.lever,
|
|
210
|
+
connectionMode: session.connectionMode,
|
|
211
|
+
walletId: currentWallet.id,
|
|
212
|
+
injectedId: solanaInjectedId,
|
|
213
|
+
fromNamespace: previousNamespace,
|
|
214
|
+
toNamespace: network.namespace,
|
|
215
|
+
durationMs: result.reassert.durationMs,
|
|
216
|
+
...(result.reassert.error && {
|
|
217
|
+
error: { type: 'unknown' as const, message: result.reassert.error }
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
175
222
|
// Filter available addresses to only include those for configured networks
|
|
176
223
|
const filteredAvailableAddresses = result.availableAddresses
|
|
177
224
|
? result.availableAddresses.filter(addr =>
|
|
@@ -71,13 +71,21 @@ export class SignatureService {
|
|
|
71
71
|
timeoutMs: this.walletResponseTimeoutMs,
|
|
72
72
|
signal: options?.signal
|
|
73
73
|
},
|
|
74
|
-
sessionHandoffContext(
|
|
74
|
+
sessionHandoffContext(
|
|
75
|
+
'signMessage',
|
|
76
|
+
session,
|
|
77
|
+
this.usingIntegratedBrowser
|
|
78
|
+
),
|
|
75
79
|
// tonConnect resolves undefined and keeps its message-only call shape.
|
|
76
80
|
() =>
|
|
77
81
|
provider === undefined
|
|
78
82
|
? connector.signMessage!(message)
|
|
79
83
|
: connector.signMessage!(message, provider)
|
|
80
84
|
)
|
|
85
|
+
// Record the namespace just signed — the parking proxy for the next op.
|
|
86
|
+
this.sessionManager.updateSession({
|
|
87
|
+
lastSignedNamespace: session.activeNetwork?.namespace ?? null
|
|
88
|
+
})
|
|
81
89
|
// AFTER the funnel: the wallet settled, so its truthful terminal is already
|
|
82
90
|
// recorded; an abort that raced completion still rejects the caller here.
|
|
83
91
|
options?.signal?.throwIfAborted()
|
|
@@ -112,12 +120,16 @@ export class SignatureService {
|
|
|
112
120
|
provider = resolveSessionProvider(session, this.usingIntegratedBrowser)
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
|
|
123
|
+
const signature = await instrumentHandoff(
|
|
116
124
|
{
|
|
117
125
|
eventManager: this.eventManager,
|
|
118
126
|
timeoutMs: this.walletResponseTimeoutMs
|
|
119
127
|
},
|
|
120
|
-
sessionHandoffContext(
|
|
128
|
+
sessionHandoffContext(
|
|
129
|
+
'signTypedData',
|
|
130
|
+
session,
|
|
131
|
+
this.usingIntegratedBrowser
|
|
132
|
+
),
|
|
121
133
|
() =>
|
|
122
134
|
session.connectionMode === 'injected'
|
|
123
135
|
? connector.signTypedData!(
|
|
@@ -128,5 +140,8 @@ export class SignatureService {
|
|
|
128
140
|
)
|
|
129
141
|
: connector.signTypedData!(typedData)
|
|
130
142
|
)
|
|
143
|
+
// signTypedData is EVM-only — records eip155 as the last-signed namespace.
|
|
144
|
+
this.sessionManager.updateSession({ lastSignedNamespace: 'eip155' })
|
|
145
|
+
return signature
|
|
131
146
|
}
|
|
132
147
|
}
|
|
@@ -90,13 +90,22 @@ export class TransactionService {
|
|
|
90
90
|
timeoutMs: this.walletResponseTimeoutMs,
|
|
91
91
|
signal: options?.signal
|
|
92
92
|
},
|
|
93
|
-
sessionHandoffContext(
|
|
93
|
+
sessionHandoffContext(
|
|
94
|
+
'sendTransaction',
|
|
95
|
+
session,
|
|
96
|
+
this.usingIntegratedBrowser
|
|
97
|
+
),
|
|
94
98
|
// tonConnect resolves undefined and keeps its request-only call shape.
|
|
95
99
|
() =>
|
|
96
100
|
provider === undefined
|
|
97
101
|
? connector.sendTransaction!(request)
|
|
98
102
|
: connector.sendTransaction!(request, provider)
|
|
99
103
|
)
|
|
104
|
+
// Record the namespace just signed — the parking proxy the NEXT op's handoff
|
|
105
|
+
// context reads (e.g. a following EVM switch/send after a Solana transfer).
|
|
106
|
+
this.sessionManager.updateSession({
|
|
107
|
+
lastSignedNamespace: session.activeNetwork?.namespace ?? null
|
|
108
|
+
})
|
|
100
109
|
// AFTER the funnel: the tx already broadcast, so its truthful terminal is
|
|
101
110
|
// recorded; an abort that raced completion still rejects the caller here.
|
|
102
111
|
options?.signal?.throwIfAborted()
|
|
@@ -122,13 +131,20 @@ export class TransactionService {
|
|
|
122
131
|
)
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
const signature = await instrumentHandoff(
|
|
126
135
|
{
|
|
127
136
|
eventManager: this.eventManager,
|
|
128
137
|
timeoutMs: this.walletResponseTimeoutMs
|
|
129
138
|
},
|
|
130
|
-
sessionHandoffContext(
|
|
139
|
+
sessionHandoffContext(
|
|
140
|
+
'signSolanaTransaction',
|
|
141
|
+
session,
|
|
142
|
+
this.usingIntegratedBrowser
|
|
143
|
+
),
|
|
131
144
|
() => connector.signSolanaTransactionBytes!(serializedTx)
|
|
132
145
|
)
|
|
146
|
+
// Sign-only on Solana still parks the wallet's active namespace on Solana.
|
|
147
|
+
this.sessionManager.updateSession({ lastSignedNamespace: 'solana' })
|
|
148
|
+
return signature
|
|
133
149
|
}
|
|
134
150
|
}
|
|
@@ -317,6 +317,10 @@ export class UniversalWalletConnector {
|
|
|
317
317
|
// Legacy-mode hang).
|
|
318
318
|
onSecondarySolanaConnectSkipped: info =>
|
|
319
319
|
this.eventManager.emit('secondarySolanaConnectSkipped', info),
|
|
320
|
+
// Positive counterpart (ONC-231): a legacy-`injectedId` wallet's secondary
|
|
321
|
+
// Solana address WAS gathered — confirms the framed gather works.
|
|
322
|
+
onSecondarySolanaConnectGathered: info =>
|
|
323
|
+
this.eventManager.emit('secondarySolanaConnectGathered', info),
|
|
320
324
|
// The wallet-switch guard released a different wallet's live state —
|
|
321
325
|
// surface it so the otherwise-silent release is observable in prod.
|
|
322
326
|
onWalletConnectionsReleased: info =>
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated from package.json by scripts/generate-version.mjs — do not edit.
|
|
2
|
-
export const UWC_CORE_VERSION = '1.
|
|
2
|
+
export const UWC_CORE_VERSION = '1.10.0'
|