@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.
- package/README.md +247 -76
- package/dist/events.d.ts +241 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +77 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/create-logger.d.ts +32 -0
- package/dist/logger/create-logger.d.ts.map +1 -0
- package/dist/logger/create-logger.js +79 -0
- package/dist/logger/create-logger.js.map +1 -0
- package/dist/managers/event-manager.d.ts.map +1 -1
- package/dist/managers/event-manager.js +5 -1
- package/dist/managers/event-manager.js.map +1 -1
- package/dist/observability/connect-observer.d.ts +33 -0
- package/dist/observability/connect-observer.d.ts.map +1 -0
- package/dist/observability/connect-observer.js +115 -0
- package/dist/observability/connect-observer.js.map +1 -0
- package/dist/observability/focus-tracker.d.ts +42 -0
- package/dist/observability/focus-tracker.d.ts.map +1 -0
- package/dist/observability/focus-tracker.js +57 -0
- package/dist/observability/focus-tracker.js.map +1 -0
- package/dist/observability/instrument-handoff.d.ts +54 -0
- package/dist/observability/instrument-handoff.d.ts.map +1 -0
- package/dist/observability/instrument-handoff.js +173 -0
- package/dist/observability/instrument-handoff.js.map +1 -0
- package/dist/observability/lifecycle-bridge.d.ts +69 -0
- package/dist/observability/lifecycle-bridge.d.ts.map +1 -0
- package/dist/observability/lifecycle-bridge.js +155 -0
- package/dist/observability/lifecycle-bridge.js.map +1 -0
- package/dist/observability/scrub.d.ts +31 -0
- package/dist/observability/scrub.d.ts.map +1 -0
- package/dist/observability/scrub.js +167 -0
- package/dist/observability/scrub.js.map +1 -0
- package/dist/observability/telemetry.d.ts +157 -0
- package/dist/observability/telemetry.d.ts.map +1 -0
- package/dist/observability/telemetry.js +373 -0
- package/dist/observability/telemetry.js.map +1 -0
- package/dist/services/connection-service.d.ts +23 -1
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +128 -19
- package/dist/services/connection-service.js.map +1 -1
- package/dist/services/network-switch-service.d.ts +2 -1
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +45 -16
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/services/signature-service.d.ts +14 -7
- package/dist/services/signature-service.d.ts.map +1 -1
- package/dist/services/signature-service.js +56 -58
- package/dist/services/signature-service.js.map +1 -1
- package/dist/services/transaction-service.d.ts +19 -5
- package/dist/services/transaction-service.d.ts.map +1 -1
- package/dist/services/transaction-service.js +55 -38
- package/dist/services/transaction-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts +56 -1
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +207 -110
- package/dist/universal-wallet-connector.js.map +1 -1
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +41 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/resolve-session-provider.d.ts +10 -0
- package/dist/utils/resolve-session-provider.d.ts.map +1 -0
- package/dist/utils/resolve-session-provider.js +21 -0
- package/dist/utils/resolve-session-provider.js.map +1 -0
- package/dist/utils/to-wallet-error.d.ts +35 -0
- package/dist/utils/to-wallet-error.d.ts.map +1 -0
- package/dist/utils/to-wallet-error.js +130 -0
- package/dist/utils/to-wallet-error.js.map +1 -0
- package/dist/utils/validate-wallet-session.d.ts +15 -0
- package/dist/utils/validate-wallet-session.d.ts.map +1 -0
- package/dist/utils/validate-wallet-session.js +29 -0
- package/dist/utils/validate-wallet-session.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +6 -6
- package/src/events.ts +326 -14
- package/src/index.ts +10 -0
- package/src/logger/create-logger.test.ts +126 -0
- package/src/logger/create-logger.ts +112 -0
- package/src/managers/event-manager.test.ts +55 -0
- package/src/managers/event-manager.ts +5 -1
- package/src/observability/connect-observer.test.ts +214 -0
- package/src/observability/connect-observer.ts +128 -0
- package/src/observability/focus-tracker.test.ts +191 -0
- package/src/observability/focus-tracker.ts +96 -0
- package/src/observability/instrument-handoff.test.ts +397 -0
- package/src/observability/instrument-handoff.ts +231 -0
- package/src/observability/lifecycle-bridge.test.ts +398 -0
- package/src/observability/lifecycle-bridge.ts +216 -0
- package/src/observability/scrub.test.ts +183 -0
- package/src/observability/scrub.ts +174 -0
- package/src/observability/telemetry.test.ts +549 -0
- package/src/observability/telemetry.ts +546 -0
- package/src/services/connection-service.test.ts +261 -1
- package/src/services/connection-service.ts +167 -38
- package/src/services/network-switch-service.test.ts +51 -1
- package/src/services/network-switch-service.ts +56 -24
- package/src/services/signature-service.test.ts +181 -76
- package/src/services/signature-service.ts +75 -85
- package/src/services/transaction-service.test.ts +270 -69
- package/src/services/transaction-service.ts +74 -51
- package/src/universal-wallet-connector.instrumentation-guard.test.ts +199 -0
- package/src/universal-wallet-connector.observability.test.ts +610 -0
- package/src/universal-wallet-connector.test.ts +27 -146
- package/src/universal-wallet-connector.ts +279 -142
- package/src/utils/id.test.ts +15 -0
- package/src/utils/id.ts +48 -0
- package/src/utils/resolve-session-provider.test.ts +54 -0
- package/src/utils/resolve-session-provider.ts +25 -0
- package/src/utils/to-wallet-error.test.ts +181 -0
- package/src/utils/to-wallet-error.ts +152 -0
- package/src/utils/validate-wallet-session.test.ts +77 -0
- package/src/utils/validate-wallet-session.ts +40 -0
- package/src/version.test.ts +11 -0
- package/src/version.ts +2 -0
|
@@ -1,89 +1,112 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ConnectionMode,
|
|
3
3
|
Connector,
|
|
4
|
-
ExtensionInjectedProvider,
|
|
5
|
-
IntegratedBrowserInjectedProvider,
|
|
6
|
-
WalletConnectProvider,
|
|
7
|
-
SupportedProvider,
|
|
8
4
|
TransactionRequest,
|
|
9
5
|
TransactionResult
|
|
10
6
|
} from '@meshconnect/uwc-types'
|
|
11
7
|
import type { SessionManager } from '../managers/session-manager'
|
|
8
|
+
import type { EventManager } from '../managers/event-manager'
|
|
12
9
|
import type { ServiceOptions } from './connection-service'
|
|
10
|
+
import {
|
|
11
|
+
instrumentHandoff,
|
|
12
|
+
sessionHandoffContext
|
|
13
|
+
} from '../observability/instrument-handoff'
|
|
14
|
+
import { resolveSessionProvider } from '../utils/resolve-session-provider'
|
|
15
|
+
import {
|
|
16
|
+
validateWalletSession,
|
|
17
|
+
getSessionConnector
|
|
18
|
+
} from '../utils/validate-wallet-session'
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
* Service for handling transaction operations across different connector types
|
|
21
|
+
* Service for handling transaction operations across different connector types.
|
|
22
|
+
*
|
|
23
|
+
* Owns the WHOLE wallet-boundary op — validation (session guards, connector
|
|
24
|
+
* capability, provider resolution) BEFORE the funnel, then the instrumented
|
|
25
|
+
* connector call — the same layering as ConnectionService / NetworkSwitchService,
|
|
26
|
+
* so a pre-wallet failure never emits an `awaitingWallet`/terminal pair.
|
|
16
27
|
*/
|
|
17
28
|
export class TransactionService {
|
|
18
29
|
constructor(
|
|
19
30
|
private sessionManager: SessionManager,
|
|
20
|
-
private connectors: Map<ConnectionMode, Connector
|
|
31
|
+
private connectors: Map<ConnectionMode, Connector>,
|
|
32
|
+
private usingIntegratedBrowser: boolean,
|
|
33
|
+
private eventManager: EventManager,
|
|
34
|
+
// Observational wallet-response deadline for the handoff timeout marker.
|
|
35
|
+
private walletResponseTimeoutMs: number
|
|
21
36
|
) {}
|
|
22
37
|
|
|
23
38
|
/**
|
|
24
39
|
* Send a transaction with the currently connected wallet
|
|
25
40
|
* @param request The transaction request parameters
|
|
26
|
-
* @param provider The wallet provider to use for sending (required for injected connector)
|
|
27
41
|
* @param options Optional cancellation signal
|
|
28
42
|
* @returns A promise that resolves to the transaction result
|
|
29
43
|
*/
|
|
30
44
|
async sendTransaction(
|
|
31
45
|
request: TransactionRequest,
|
|
32
|
-
provider?: SupportedProvider,
|
|
33
46
|
options?: ServiceOptions
|
|
34
47
|
): Promise<TransactionResult> {
|
|
35
48
|
options?.signal?.throwIfAborted()
|
|
36
|
-
const session = this.sessionManager
|
|
49
|
+
const session = validateWalletSession(this.sessionManager, {
|
|
50
|
+
requireAddress: true
|
|
51
|
+
})
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
const connector = getSessionConnector(this.connectors, session)
|
|
54
|
+
if (!connector.sendTransaction) {
|
|
55
|
+
throw new Error(`Connector does not support transactions`)
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
// Validation, not wallet work — a missing provider must not enter the funnel.
|
|
59
|
+
// undefined only for tonConnect, whose connector resolves its own provider.
|
|
60
|
+
const provider = resolveSessionProvider(
|
|
61
|
+
session,
|
|
62
|
+
this.usingIntegratedBrowser
|
|
63
|
+
)
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
const result = await instrumentHandoff(
|
|
66
|
+
{
|
|
67
|
+
eventManager: this.eventManager,
|
|
68
|
+
timeoutMs: this.walletResponseTimeoutMs,
|
|
69
|
+
signal: options?.signal
|
|
70
|
+
},
|
|
71
|
+
sessionHandoffContext('sendTransaction', session),
|
|
72
|
+
// tonConnect resolves undefined and keeps its request-only call shape.
|
|
73
|
+
() =>
|
|
74
|
+
provider === undefined
|
|
75
|
+
? connector.sendTransaction!(request)
|
|
76
|
+
: connector.sendTransaction!(request, provider)
|
|
77
|
+
)
|
|
78
|
+
// AFTER the funnel: the tx already broadcast, so its truthful terminal is
|
|
79
|
+
// recorded; an abort that raced completion still rejects the caller here.
|
|
80
|
+
options?.signal?.throwIfAborted()
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Sign a Solana tx without broadcasting (raw bytes in/out). Routes to the
|
|
86
|
+
* active connector's optional `signSolanaTransactionBytes`; the capability
|
|
87
|
+
* check is validation and stays OUTSIDE the funnel.
|
|
88
|
+
*/
|
|
89
|
+
async signSolanaTransactionBytes(
|
|
90
|
+
serializedTx: Uint8Array
|
|
91
|
+
): Promise<Uint8Array> {
|
|
92
|
+
const session = validateWalletSession(this.sessionManager, {
|
|
93
|
+
requireAddress: false
|
|
94
|
+
})
|
|
58
95
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new Error('Provider is required for injected connector')
|
|
64
|
-
}
|
|
65
|
-
result = await connector.sendTransaction(
|
|
66
|
-
request,
|
|
67
|
-
provider as
|
|
68
|
-
| ExtensionInjectedProvider
|
|
69
|
-
| IntegratedBrowserInjectedProvider
|
|
70
|
-
)
|
|
71
|
-
} else if (session.connectionMode === 'tonConnect') {
|
|
72
|
-
result = await connector.sendTransaction(request)
|
|
73
|
-
} else if (session.connectionMode === 'walletConnect') {
|
|
74
|
-
if (!provider) {
|
|
75
|
-
throw new Error('Provider is required for WalletConnect connector')
|
|
76
|
-
}
|
|
77
|
-
result = await connector.sendTransaction(
|
|
78
|
-
request,
|
|
79
|
-
provider as WalletConnectProvider
|
|
96
|
+
const connector = this.connectors.get(session.connectionMode!)
|
|
97
|
+
if (!connector?.signSolanaTransactionBytes) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`signSolanaTransaction is not supported for connection mode "${session.connectionMode}"`
|
|
80
100
|
)
|
|
81
|
-
} else {
|
|
82
|
-
// For other connectors that might not need the provider
|
|
83
|
-
result = await connector.sendTransaction(request)
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
return await instrumentHandoff(
|
|
104
|
+
{
|
|
105
|
+
eventManager: this.eventManager,
|
|
106
|
+
timeoutMs: this.walletResponseTimeoutMs
|
|
107
|
+
},
|
|
108
|
+
sessionHandoffContext('signSolanaTransaction', session),
|
|
109
|
+
() => connector.signSolanaTransactionBytes!(serializedTx)
|
|
110
|
+
)
|
|
88
111
|
}
|
|
89
112
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import type { Network, WalletMetadata, Session } from '@meshconnect/uwc-types'
|
|
3
|
+
import { UniversalWalletConnector } from './universal-wallet-connector'
|
|
4
|
+
import { UWC_OPERATIONS } from './events'
|
|
5
|
+
import type { UWCObserver, UWCTelemetryEvent } from './observability/telemetry'
|
|
6
|
+
|
|
7
|
+
// Guardrail: every wallet-boundary op reachable through the public API MUST go
|
|
8
|
+
// through `instrumentHandoff` — i.e. emit exactly one `awaitingWallet` and
|
|
9
|
+
// exactly one terminal. A new `UWCOperation` wired up without `instrumentHandoff`
|
|
10
|
+
// (the easy mistake when adding a sign/send method) fails this test.
|
|
11
|
+
//
|
|
12
|
+
// Every instrumented op lives INSIDE its service (validation first, then the
|
|
13
|
+
// funnel at the real wallet boundary) — one layering for connect, switch, sign
|
|
14
|
+
// and send. The sign/send ops below run END-TO-END through the REAL
|
|
15
|
+
// SignatureService / TransactionService against mocked connectors; connect and
|
|
16
|
+
// switchNetwork services are mocked here, with their handoff coverage in
|
|
17
|
+
// connection-service.test.ts / network-switch-service.test.ts.
|
|
18
|
+
|
|
19
|
+
const signMessageMock = vi.fn()
|
|
20
|
+
const signTypedDataMock = vi.fn()
|
|
21
|
+
const sendTransactionMock = vi.fn()
|
|
22
|
+
const signSolanaTransactionBytesMock = vi.fn()
|
|
23
|
+
|
|
24
|
+
const connectedSession: Session = {
|
|
25
|
+
connectionMode: 'injected',
|
|
26
|
+
activeNetwork: {
|
|
27
|
+
internalId: 1,
|
|
28
|
+
id: 'eip155:1',
|
|
29
|
+
namespace: 'eip155',
|
|
30
|
+
name: 'Ethereum',
|
|
31
|
+
logoUrl: '',
|
|
32
|
+
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
33
|
+
networkType: 'evm'
|
|
34
|
+
},
|
|
35
|
+
activeWalletCapabilities: null,
|
|
36
|
+
activeNetworkWalletCapabilities: null,
|
|
37
|
+
activeAddress: '0xabc',
|
|
38
|
+
publicKey: null,
|
|
39
|
+
availableNetworks: [],
|
|
40
|
+
activeWallet: {
|
|
41
|
+
id: 'metamask',
|
|
42
|
+
name: 'MetaMask',
|
|
43
|
+
extensionInjectedProvider: {}
|
|
44
|
+
} as unknown as WalletMetadata,
|
|
45
|
+
availableAddresses: []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
vi.mock('@meshconnect/uwc-injected-connector', () => ({
|
|
49
|
+
InjectedConnector: vi.fn().mockImplementation(function () {
|
|
50
|
+
return {
|
|
51
|
+
getAvailableWallets: vi.fn().mockResolvedValue([]),
|
|
52
|
+
// The REAL services route the session's connectionMode ('injected') to
|
|
53
|
+
// this connector — these are the instrumented wallet boundaries.
|
|
54
|
+
signMessage: signMessageMock,
|
|
55
|
+
signTypedData: signTypedDataMock,
|
|
56
|
+
sendTransaction: sendTransactionMock,
|
|
57
|
+
signSolanaTransactionBytes: signSolanaTransactionBytesMock
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}))
|
|
61
|
+
vi.mock('@meshconnect/uwc-wallet-connect-connector', () => ({
|
|
62
|
+
WalletConnectConnector: vi.fn().mockImplementation(function () {
|
|
63
|
+
return { getAvailableWallets: vi.fn().mockResolvedValue([]) }
|
|
64
|
+
})
|
|
65
|
+
}))
|
|
66
|
+
vi.mock('@meshconnect/uwc-ton-connector', () => ({
|
|
67
|
+
TonConnectConnector: vi.fn().mockImplementation(function () {
|
|
68
|
+
return { getAvailableWallets: vi.fn().mockResolvedValue([]) }
|
|
69
|
+
})
|
|
70
|
+
}))
|
|
71
|
+
vi.mock('./managers/session-manager', () => ({
|
|
72
|
+
SessionManager: vi.fn().mockImplementation(function () {
|
|
73
|
+
return { getSession: () => connectedSession, updateSession: vi.fn() }
|
|
74
|
+
})
|
|
75
|
+
}))
|
|
76
|
+
vi.mock('./services/connection-service', () => ({
|
|
77
|
+
ConnectionService: vi.fn().mockImplementation(function () {
|
|
78
|
+
return {
|
|
79
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
80
|
+
disconnect: vi.fn().mockResolvedValue(undefined),
|
|
81
|
+
getWallets: vi.fn().mockReturnValue([]),
|
|
82
|
+
getNetworks: vi.fn().mockReturnValue([]),
|
|
83
|
+
getConnectionURI: vi.fn(),
|
|
84
|
+
updateWallets: vi.fn(),
|
|
85
|
+
teardownLifecycle: vi.fn()
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}))
|
|
89
|
+
vi.mock('./services/network-switch-service', () => ({
|
|
90
|
+
NetworkSwitchService: vi.fn().mockImplementation(function () {
|
|
91
|
+
return { switchNetwork: vi.fn(), getLoadingState: vi.fn() }
|
|
92
|
+
})
|
|
93
|
+
}))
|
|
94
|
+
vi.mock('./services/wallet-capabilities-service', () => ({
|
|
95
|
+
WalletCapabilitiesService: vi.fn().mockImplementation(function () {
|
|
96
|
+
return { getWalletCapabilities: vi.fn() }
|
|
97
|
+
})
|
|
98
|
+
}))
|
|
99
|
+
|
|
100
|
+
const networks = [
|
|
101
|
+
{
|
|
102
|
+
internalId: 1,
|
|
103
|
+
id: 'eip155:1',
|
|
104
|
+
namespace: 'eip155',
|
|
105
|
+
name: 'Ethereum',
|
|
106
|
+
logoUrl: '',
|
|
107
|
+
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
108
|
+
networkType: 'evm'
|
|
109
|
+
}
|
|
110
|
+
] as unknown as Network[]
|
|
111
|
+
|
|
112
|
+
const wallets = [
|
|
113
|
+
{ id: 'metamask', name: 'MetaMask' }
|
|
114
|
+
] as unknown as WalletMetadata[]
|
|
115
|
+
|
|
116
|
+
const TERMINALS = ['walletSucceeded', 'walletRejected', 'walletFailed'] as const
|
|
117
|
+
|
|
118
|
+
type OpRunner = (uwc: UniversalWalletConnector) => Promise<unknown>
|
|
119
|
+
|
|
120
|
+
const OPS: Array<{ op: string; run: OpRunner }> = [
|
|
121
|
+
{ op: 'signMessage', run: uwc => uwc.signMessage('hello') },
|
|
122
|
+
{
|
|
123
|
+
op: 'signTypedData',
|
|
124
|
+
run: uwc =>
|
|
125
|
+
uwc.signTypedData({
|
|
126
|
+
domain: {},
|
|
127
|
+
types: {},
|
|
128
|
+
primaryType: 'x',
|
|
129
|
+
message: {}
|
|
130
|
+
} as never)
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
op: 'sendTransaction',
|
|
134
|
+
run: uwc => uwc.sendTransaction({ to: '0xabc' } as never)
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
op: 'signSolanaTransaction',
|
|
138
|
+
run: uwc => uwc.signSolanaTransaction(new Uint8Array([1, 2, 3]))
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
describe('UniversalWalletConnector — instrumentation guard', () => {
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
UniversalWalletConnector.resetInstance()
|
|
145
|
+
signMessageMock.mockReset().mockResolvedValue({
|
|
146
|
+
type: 'standard',
|
|
147
|
+
signature: '0xsig'
|
|
148
|
+
})
|
|
149
|
+
signTypedDataMock.mockReset().mockResolvedValue('0xsig')
|
|
150
|
+
sendTransactionMock.mockReset().mockResolvedValue({ hash: '0xtx' })
|
|
151
|
+
signSolanaTransactionBytesMock
|
|
152
|
+
.mockReset()
|
|
153
|
+
.mockResolvedValue(new Uint8Array([9]))
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
for (const { op, run } of OPS) {
|
|
157
|
+
it(`${op} emits awaitingWallet and exactly one terminal`, async () => {
|
|
158
|
+
const events: UWCTelemetryEvent[] = []
|
|
159
|
+
const observer: UWCObserver = { onEvent: e => events.push(e) }
|
|
160
|
+
const uwc = new UniversalWalletConnector({ networks, wallets, observer })
|
|
161
|
+
|
|
162
|
+
await run(uwc)
|
|
163
|
+
|
|
164
|
+
const awaiting = events.filter(e => e.name === 'awaitingWallet')
|
|
165
|
+
const terminals = events.filter(e =>
|
|
166
|
+
TERMINALS.includes(e.name as (typeof TERMINALS)[number])
|
|
167
|
+
)
|
|
168
|
+
expect(awaiting).toHaveLength(1)
|
|
169
|
+
expect(awaiting[0].operation).toBe(op)
|
|
170
|
+
expect(terminals).toHaveLength(1)
|
|
171
|
+
expect(terminals[0].handoffId).toBe(awaiting[0].handoffId)
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// A new UWCOperation MUST be categorized here — instrumented inside a service
|
|
176
|
+
// (the OPS above run end-to-end through the real sign/send services; connect /
|
|
177
|
+
// switchNetwork are covered in their service tests) or deliberately
|
|
178
|
+
// uninstrumented (read-only / housekeeping). This partition fails on an
|
|
179
|
+
// UNcategorized addition, so a new sign/send op can't silently skip the
|
|
180
|
+
// handoff funnel just because the author forgot to extend OPS — the gap the
|
|
181
|
+
// hardcoded list alone couldn't catch.
|
|
182
|
+
it('every UWCOperation is categorized (none escapes the funnel decision)', () => {
|
|
183
|
+
const serviceInstrumented = [
|
|
184
|
+
...OPS.map(o => o.op),
|
|
185
|
+
'connect',
|
|
186
|
+
'switchNetwork'
|
|
187
|
+
]
|
|
188
|
+
const deliberatelyUninstrumented = [
|
|
189
|
+
'disconnect',
|
|
190
|
+
'initialize',
|
|
191
|
+
'getWalletCapabilities'
|
|
192
|
+
]
|
|
193
|
+
const categorized = [
|
|
194
|
+
...serviceInstrumented,
|
|
195
|
+
...deliberatelyUninstrumented
|
|
196
|
+
].sort()
|
|
197
|
+
expect(categorized).toEqual([...UWC_OPERATIONS].sort())
|
|
198
|
+
})
|
|
199
|
+
})
|