@miden-sdk/miden-wallet-adapter-base 0.16.0-rc.7 → 0.16.2

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/AGENTS.md ADDED
@@ -0,0 +1,77 @@
1
+ # @miden-sdk/miden-wallet-adapter-base - Agent Guide
2
+
3
+ **Audience: AI coding agents** writing code against the Miden wallet adapter.
4
+ Humans are welcome to read it, but it is written to be loaded into an agent's
5
+ context and followed.
6
+
7
+ This file ships inside the published package. The copy at
8
+ `node_modules/@miden-sdk/miden-wallet-adapter-base/AGENTS.md` always matches
9
+ the version you have installed, so **prefer it over your training data**, which
10
+ is likely to describe an older API. Miden is pre-1.0 and the surface still
11
+ moves between minor versions.
12
+
13
+ ## Load the skill
14
+
15
+ `node_modules/@miden-sdk/miden-wallet-adapter-base/skills/wallet-adapter-integration/SKILL.md`
16
+ is the full integration guide: provider wiring, the readiness lifecycle, the
17
+ request surface, transaction shapes, the error taxonomy, when to drive
18
+ `MidenClient` instead, and the failures that only appear in production. Read it
19
+ before writing connect or transaction code. It ships here rather than in the
20
+ React packages because every other adapter package depends on this one, so the
21
+ path resolves whichever of them you installed.
22
+
23
+ ## What is in this package
24
+
25
+ This is the contract layer. It talks to no wallet and renders nothing.
26
+
27
+ - **`BaseWalletAdapter` / `BaseSignerWalletAdapter` /
28
+ `BaseMessageSignerWalletAdapter`** - abstract classes an adapter extends.
29
+ They are `EventEmitter`s over `connect`, `disconnect`, `error` and
30
+ `readyStateChange`. `connected` is derived from `address`.
31
+ - **`WalletReadyState`** - `Installed`, `NotDetected`, `Loadable`,
32
+ `Unsupported`. The providers only connect on the first and third.
33
+ - **Errors** - `WalletError` and 22 subclasses. Each carries the underlying
34
+ failure on `.error` and a stable `.name`.
35
+ - **Transactions** - `SendTransaction`, `ConsumeTransaction`,
36
+ `CustomTransaction`, plus the `Transaction` envelope and `TransactionType`.
37
+ - **Enums** - `WalletAdapterNetwork` (`Devnet`, `Testnet`, `Localnet`),
38
+ `PrivateDataPermission`, `AllowedPrivateData`.
39
+ - **Helpers** - `scopePollingDetectionStrategy` for injection detection,
40
+ `u8ToB64` and `b64ToU8` for the wire encoding.
41
+
42
+ ## Rules that are easy to get wrong
43
+
44
+ **Amounts here are `number`, not `bigint`.** `SendTransaction` and
45
+ `ConsumeTransaction` take a plain number, unlike `@miden-sdk/miden-sdk`, where
46
+ amounts are always `BigInt`. Convert at the boundary and do not copy a bigint
47
+ in from SDK code.
48
+
49
+ **Only six of the error classes are ever thrown** by the packages in this
50
+ family: `WalletNotSelectedError`, `WalletNotReadyError`,
51
+ `WalletConnectionError`, `WalletNotConnectedError`, `WalletTransactionError`
52
+ and `WalletDisconnectionError`. The rest are vocabulary for other adapters.
53
+ Writing a handler that waits for `WalletTimeoutError` waits forever.
54
+
55
+ **`AllowedPrivateData` is a bitmask.** `None`, `Assets`, `Notes`, `Storage`,
56
+ `All`. Combine with `|`. It decides what the wallet will answer for assets,
57
+ notes and storage, so a connect with `None` makes every read come back empty
58
+ rather than failing loudly.
59
+
60
+ **`CustomTransaction` serializes eagerly.** Its constructor calls
61
+ `serialize()` on the `TransactionRequest` you pass, so hand it a live object
62
+ from `@miden-sdk/miden-sdk`, not bytes you already encoded.
63
+
64
+ **This package has no runtime dependency on the SDK.** It imports
65
+ `@miden-sdk/miden-sdk` for types only, and declares it as a peer dependency.
66
+ The concrete adapter in `@miden-sdk/miden-wallet-adapter-miden` is the one
67
+ that pulls the WASM entry point in.
68
+
69
+ ## Going deeper
70
+
71
+ - The type declarations shipped in `dist/` are authoritative for signatures.
72
+ When this guide and the types disagree, the types are right and this file is
73
+ a bug.
74
+ - The client behind a wallet-signed transaction documents itself at
75
+ `node_modules/@miden-sdk/miden-sdk/AGENTS.md`.
76
+ - Breaking changes and migration notes: the `CHANGELOG.md` in
77
+ [`0xMiden/web-sdk`](https://github.com/0xMiden/web-sdk).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miden-sdk/miden-wallet-adapter-base",
3
- "version": "0.16.0-rc.7",
3
+ "version": "0.16.2",
4
4
  "description": "Core infrastructure for connecting Miden-compatible wallets to your dApp.",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,13 +17,13 @@
17
17
  "devDependencies": {
18
18
  "jsdom": "^24.0.0",
19
19
  "vitest": "^1.0.0",
20
- "@miden-sdk/miden-sdk": "0.16.0-rc.7"
20
+ "@miden-sdk/miden-sdk": "0.16.2"
21
21
  },
22
22
  "bugs": {
23
23
  "url": "https://github.com/0xMiden/web-sdk/issues"
24
24
  },
25
25
  "peerDependencies": {
26
- "@miden-sdk/miden-sdk": "^0.16.0-rc.7"
26
+ "@miden-sdk/miden-sdk": "^0.16.2"
27
27
  },
28
28
  "homepage": "https://github.com/0xMiden/web-sdk",
29
29
  "scripts": {
@@ -0,0 +1,335 @@
1
+ ---
2
+ name: wallet-adapter-integration
3
+ description: Connect a dApp to a Miden wallet with @miden-sdk/miden-wallet-adapter. Covers provider wiring, the WalletReadyState lifecycle, the request* surface and transaction shapes, the Wallet*Error taxonomy, the wallet-as-signer path into MidenClient, and the traps that only show up in production. Use when adding wallet connect to an app, debugging a connect or transaction failure, or writing a new wallet adapter.
4
+ ---
5
+
6
+ # Miden wallet adapter integration
7
+
8
+ The adapter is a thin, typed channel between your page and a wallet that
9
+ injects itself into `window`. It holds no keys, no client and no chain state.
10
+ Every call is a request the wallet may prompt on, refuse, or answer from its
11
+ own store.
12
+
13
+ ## Pick the package
14
+
15
+ | Package | Install when |
16
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
17
+ | `@miden-sdk/miden-wallet-adapter` | React app. Re-exports the four below and serves the stylesheet at `@miden-sdk/miden-wallet-adapter/styles.css`. |
18
+ | `@miden-sdk/miden-wallet-adapter-base` | No React, or you are writing an adapter. Types, errors, transaction shapes, `BaseWalletAdapter`. |
19
+ | `@miden-sdk/miden-wallet-adapter-miden` | `MidenWalletAdapter`, the adapter for the Bread wallet extension. |
20
+ | `@miden-sdk/miden-wallet-adapter-react` | `WalletProvider`, `useWallet`, `MidenFiSignerProvider`. |
21
+ | `@miden-sdk/miden-wallet-adapter-reactui` | Prebuilt connect button, dropdown and wallet modal. |
22
+
23
+ Install the peers yourself: `@miden-sdk/miden-sdk` (base, miden and react all
24
+ declare it as a peer dependency) and, for anything that pulls the react
25
+ package, `@miden-sdk/react`. The react package's entry point imports
26
+ `SignerContext` from `@miden-sdk/react` even if you only ever call
27
+ `useWallet`, so the module must resolve at build time.
28
+
29
+ ## Wiring: two shapes
30
+
31
+ ### A. The wallet does the work
32
+
33
+ `WalletProvider` owns wallet selection and connection state. `useWallet`
34
+ exposes it.
35
+
36
+ ```tsx
37
+ // wallets.ts - module scope, so the array identity never changes
38
+ import { MidenWalletAdapter } from "@miden-sdk/miden-wallet-adapter";
39
+ export const wallets = [new MidenWalletAdapter({ appName: "My dApp" })];
40
+ ```
41
+
42
+ ```tsx
43
+ import {
44
+ AllowedPrivateData,
45
+ PrivateDataPermission,
46
+ WalletAdapterNetwork,
47
+ WalletModalProvider,
48
+ WalletMultiButton,
49
+ WalletProvider,
50
+ } from "@miden-sdk/miden-wallet-adapter";
51
+ import "@miden-sdk/miden-wallet-adapter/styles.css";
52
+ import { wallets } from "./wallets";
53
+
54
+ export function App() {
55
+ return (
56
+ <WalletProvider
57
+ wallets={wallets}
58
+ network={WalletAdapterNetwork.Testnet}
59
+ privateDataPermission={PrivateDataPermission.UponRequest}
60
+ allowedPrivateData={AllowedPrivateData.Assets | AllowedPrivateData.Notes}
61
+ onError={(error) => console.error(error.name, error.message)}
62
+ >
63
+ <WalletModalProvider>
64
+ <WalletMultiButton />
65
+ </WalletModalProvider>
66
+ </WalletProvider>
67
+ );
68
+ }
69
+ ```
70
+
71
+ `network`, `privateDataPermission` and `allowedPrivateData` belong on the
72
+ provider. `connect()` takes no arguments in practice: the provider's `connect`
73
+ ignores anything passed to it and forwards its own props to the adapter.
74
+
75
+ ### B. The wallet holds the key, your app drives `MidenClient`
76
+
77
+ `MidenFiSignerProvider` does everything `WalletProvider` does and also
78
+ publishes a `SignerContext` for `@miden-sdk/react`, so `MidenProvider` builds
79
+ a client whose signing callback is the wallet's `signBytes`.
80
+
81
+ ```tsx
82
+ import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react";
83
+ import { WalletAdapterNetwork } from "@miden-sdk/miden-wallet-adapter-base";
84
+ import { MidenProvider } from "@miden-sdk/react";
85
+
86
+ <MidenFiSignerProvider appName="My dApp" network={WalletAdapterNetwork.Testnet}>
87
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
88
+ <App />
89
+ </MidenProvider>
90
+ </MidenFiSignerProvider>;
91
+ ```
92
+
93
+ `MidenProvider` must be a descendant, not an ancestor: the signer context is
94
+ provided by `MidenFiSignerProvider` and consumed by `MidenProvider`.
95
+
96
+ It creates a `MidenWalletAdapter` for you when you pass no `wallets`, and
97
+ auto-selects the wallet when exactly one is available. It also provides the
98
+ same context `useWallet` and every reactui component read, so the buttons and
99
+ the modal work underneath it.
100
+
101
+ The account it configures is the connected one: `accountConfig.importAccountId`
102
+ defaults to the wallet's `address`, `publicKeyCommitment` is the adapter's
103
+ `publicKey`, and the IndexedDB store is named `midenfi_<address>`. Override
104
+ with `importAccountId`, `accountType` (default
105
+ `"RegularAccountImmutableCode"`), `storageMode` (`"private" | "public"`,
106
+ default `"public"`) and `customComponents`.
107
+
108
+ Read wallet state inside it with `useMidenFiWallet()`, which throws if it is
109
+ rendered outside the provider, or with `useWallet()` from the same package.
110
+
111
+ ## The readiness lifecycle
112
+
113
+ `WalletReadyState` on the adapter, mirrored per wallet in `useWallet().wallets`:
114
+
115
+ | State | Meaning |
116
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
117
+ | `Unsupported` | No DOM. `MidenWalletAdapter` enters this state when `window` or `document` is undefined at construction, and never leaves it. |
118
+ | `NotDetected` | DOM present, nothing injected yet. The starting state in a browser. |
119
+ | `Installed` | The wallet injected itself. `MidenWalletAdapter` polls for `window.midenWallet` or `window.miden` once a second, on `DOMContentLoaded`, on `load` and immediately, then emits `readyStateChange`. |
120
+ | `Loadable` | A wallet that needs no install. No adapter in this repo reports it, but the providers treat it as connectable. |
121
+
122
+ The connect path is: `select(name)` writes the wallet name to
123
+ `localStorage` (key `walletName`, override with `localStorageKey`), the
124
+ provider resolves it to an adapter, then `connect()` drives the handshake.
125
+ With `WalletProvider` you must select first or `connect()` throws
126
+ `WalletNotSelectedError`.
127
+
128
+ If `readyState` is neither `Installed` nor `Loadable`, the provider's
129
+ `connect()` clears the stored name, opens `adapter.url` in a new tab (the
130
+ extension's store listing) and throws `WalletNotReadyError`.
131
+
132
+ `autoConnect` is off by default. When on, it connects as soon as a stored
133
+ wallet resolves to a ready adapter, and clears the stored name if that fails.
134
+ It never rethrows; the failure reaches you through `onError` only.
135
+
136
+ Connecting twice is deliberately not a no-op. A second `connect()` re-drives
137
+ the handshake so a dApp whose JS context survived a park and restore gets its
138
+ account back. The cost is that a wallet that locked in the meantime may
139
+ prompt for unlock.
140
+
141
+ ## Calling the wallet
142
+
143
+ `useWallet()` and `useMidenFiWallet()` return each method as
144
+ `fn | undefined`. They are `undefined` until a wallet is selected, and reject
145
+ with `WalletNotConnectedError` while disconnected. Always guard.
146
+
147
+ | Method | Returns | Notes |
148
+ | --------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
149
+ | `requestSend(tx)` | transaction id | `MidenSendTransaction` payload. |
150
+ | `requestConsume(tx)` | transaction id | `MidenConsumeTransaction` payload. |
151
+ | `requestTransaction(tx)` | transaction id | A `MidenTransaction` envelope. Dispatches by `type` to the send and consume endpoints; anything else goes to the wallet's generalized endpoint. |
152
+ | `waitForTransaction(txId, timeout?)` | `{ txHash, outputNotes }` | `outputNotes` are `Note` objects from `@miden-sdk/miden-sdk`. |
153
+ | `requestAssets()` | `{ faucetId, amount }[]` | `amount` is a string. |
154
+ | `requestConsumableNotes()` | `InputNoteDetails[]` | From the wallet's store. |
155
+ | `requestPrivateNotes(filter, noteIds?)` | `InputNoteDetails[]` | `filter` is a `NoteFilterTypes` from `@miden-sdk/miden-sdk`. |
156
+ | `importPrivateNote(bytes)` | note id | `bytes` is a serialized note. |
157
+ | `signBytes(data, kind)` | `Uint8Array` | `kind` is `"word"` or `"signingInputs"`. |
158
+ | `createAccount(params?)` | account id | Only on `MidenFiSignerProvider`'s context. See the traps. |
159
+ | `requestGuardianInfo()` | `GuardianInfo` | Whether the connected account is a guardian account, and its endpoint, provider and sync status. |
160
+
161
+ Everything the wallet returns about private state is gated by the
162
+ `AllowedPrivateData` bitmask you connected with: `None`, `Assets`, `Notes`,
163
+ `Storage`, `All`. Combine with `|`. `PrivateDataPermission.UponRequest` makes
164
+ the wallet ask every time; `Auto` lets it answer without a prompt.
165
+
166
+ ## Transaction shapes
167
+
168
+ All three are plain objects you can build by hand, with classes and static
169
+ factories in `@miden-sdk/miden-wallet-adapter-base` that build them for you.
170
+ The constructor signatures:
171
+
172
+ ```
173
+ SendTransaction(sender, recipient, faucetId, noteType, amount, recallBlocks?)
174
+ ConsumeTransaction(faucetId, noteId, noteType, amount, noteBytes?)
175
+ CustomTransaction(address, recipientAddress, transactionRequest,
176
+ inputNoteIds?, inputNoteBytes?)
177
+ ```
178
+
179
+ `amount` is a `number`, not a `bigint`, unlike everything in
180
+ `@miden-sdk/miden-sdk`.
181
+
182
+ `noteType` is the string `"public"` or `"private"`. `ConsumeTransaction` and
183
+ `CustomTransaction` base64-encode the `Uint8Array` arguments for you, and
184
+ `CustomTransaction` calls `transactionRequest.serialize()` on the live
185
+ `TransactionRequest` you hand it, so build that with `@miden-sdk/miden-sdk`
186
+ first.
187
+
188
+ `requestTransaction` wants the envelope, not a bare payload:
189
+
190
+ ```ts
191
+ import { Transaction } from "@miden-sdk/miden-wallet-adapter";
192
+
193
+ const tx = Transaction.createCustomTransaction(
194
+ address,
195
+ recipientAddress,
196
+ transactionRequest
197
+ );
198
+ const txId = await requestTransaction(tx);
199
+ ```
200
+
201
+ `Transaction.createSendTransaction` and `Transaction.createConsumeTransaction`
202
+ build the other two envelopes, and the adapter routes them back to the
203
+ dedicated endpoints.
204
+
205
+ ## Confirming a transaction
206
+
207
+ The id a request resolves with names a transaction the wallet **accepted**,
208
+ not one that landed. Poll for the on-chain result:
209
+
210
+ ```ts
211
+ const txId = await requestSend({
212
+ senderAddress: address,
213
+ recipientAddress: recipient,
214
+ faucetId,
215
+ noteType: "public",
216
+ amount: 100,
217
+ });
218
+ const { txHash, outputNotes } = await waitForTransaction(txId);
219
+ ```
220
+
221
+ A wallet that resolves with no id at all is treated as a silent drop and
222
+ throws `WalletTransactionError`. A wallet that reports a failed transaction
223
+ from `waitForTransaction` also throws `WalletTransactionError`, carrying the
224
+ wallet's `errorMessage`.
225
+
226
+ ## Error taxonomy
227
+
228
+ Every error extends `WalletError`, which carries the underlying failure on
229
+ `.error` and a stable `.name`. Switch on `error.name` or use `instanceof`.
230
+
231
+ | Error | Thrown when | Do |
232
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
233
+ | `WalletNotSelectedError` | `connect()` with no wallet selected. | Call `select(name)`, or open the modal. |
234
+ | `WalletNotReadyError` | `connect()` while the adapter is not `Installed` or `Loadable`. The provider has already opened the install page. | Tell the user to install or unlock, then retry. |
235
+ | `WalletConnectionError` | The wallet rejected the handshake, or returned no address. | Surface it. A rejection here is usually the user declining. |
236
+ | `WalletNotConnectedError` | Any request while no wallet is connected. | Gate your UI on `connected`. |
237
+ | `WalletTransactionError` | The wallet rejected a request, returned no transaction id, or reported a failed transaction. | Read `.error` for the wallet's own message. |
238
+ | `WalletDisconnectionError` | `disconnect()` failed wallet-side. Emitted on the `error` event, never thrown: `disconnect()` still resolves and still emits `disconnect`. | Do not rely on try/catch around `disconnect()`. |
239
+
240
+ `base` exports many more (`WalletLoadError`, `WalletConfigError`,
241
+ `WalletDisconnectedError`, `WalletAccountError`, `WalletAddressError`,
242
+ `WalletKeypairError`, `WalletSendTransactionError`, `WalletSignMessageError`,
243
+ `WalletSignTransactionError`, `WalletTimeoutError`, `WalletWindowBlockedError`,
244
+ `WalletWindowClosedError`, `WalletDecryptionError`,
245
+ `WalletDecryptionNotAllowedError`, `WalletPrivateDataPermissionError`,
246
+ `WalletRecordsError`). Nothing in these packages throws them; they exist for
247
+ other adapters to use. Do not write handlers that wait for them.
248
+
249
+ Errors reach you twice: the provider calls `onError` (or `console.error` when
250
+ you pass none) **and** the promise rejects. Report in one place or the user
251
+ sees the same failure twice. `importPrivateNote` and `requestConsumableNotes`
252
+ emit the wallet's raw error rather than a `WalletError` subclass, so an
253
+ `onError` handler must not assume `error.name` is one of the names above.
254
+
255
+ ## Adapter or `MidenClient` directly?
256
+
257
+ - **Wallet builds, signs, submits.** `requestSend`, `requestConsume`,
258
+ `requestTransaction`. The wallet shows its own confirmation UI and your app
259
+ never touches a key or a prover. Simplest, and the right default.
260
+ - **Your app builds, the wallet signs.** `MidenFiSignerProvider` plus
261
+ `MidenProvider`. You construct `TransactionRequest`s with
262
+ `@miden-sdk/miden-sdk` and the client calls back into `signBytes` for the
263
+ signature. Reach for this when you need transaction shapes the wallet's
264
+ endpoints do not express, or you want the client's local state.
265
+ - **Reads.** `requestAssets`, `requestConsumableNotes` and
266
+ `requestPrivateNotes` answer from the wallet's store and only within the
267
+ permission you connected with. If you need chain state your app controls,
268
+ sync your own client instead.
269
+
270
+ The two are not exclusive: a `CustomTransaction` carries a serialized
271
+ `TransactionRequest`, so building one already means having the SDK loaded.
272
+
273
+ ## Traps
274
+
275
+ **Construct adapters once.** An adapter array built inline in JSX is a new
276
+ array with a new adapter on every render. The provider rewraps its wallet
277
+ list, the selected adapter's identity changes, and the effect that disconnects
278
+ the previous adapter fires. Module scope, or `useMemo` with a stable
279
+ dependency list.
280
+
281
+ **An adapter constructed without a DOM is `Unsupported` forever.** The
282
+ constructor decides `readyState` once and only starts detection when `window`
283
+ and `document` exist. Under SSR, construct adapters in a client-only module.
284
+
285
+ **Server and client render different wallet state.** Wallet selection is read
286
+ from `localStorage` inside a `useState` initializer. On the server that read
287
+ fails and is swallowed, so the server renders "no wallet". Gate
288
+ wallet-dependent markup on a mounted flag if you server-render.
289
+
290
+ **The miden package imports the SDK eagerly.** `MidenWalletAdapter` imports
291
+ `@miden-sdk/miden-sdk` at module scope to deserialize output notes, which
292
+ pulls the eager WASM entry point and its top-level await into any module graph
293
+ that reaches it. Keep it out of server-rendered modules.
294
+
295
+ **`connect()` arguments are dropped.** The reactui buttons and modal accept
296
+ `privateDataPermission`, `network` and `allowedPrivateData` props and pass
297
+ them to `connect()`, which ignores them. Whatever you set on the provider is
298
+ what the wallet is asked for. Setting the network on the modal and not on the
299
+ provider is a silent testnet default.
300
+
301
+ **`createAccount` is on the interface but not in the wallets.** The package
302
+ ships a conformance suite (`getSurfaceCases`, `getBehaviorCases`,
303
+ `runConformance` from the miden package) precisely because this method has
304
+ been on the published `MidenWallet` interface with no provider implementing it
305
+ and no wire message behind it. Calling it yields
306
+ `TypeError: wallet.createAccount is not a function`. It is also absent from
307
+ `WalletProvider`'s context; only `MidenFiSignerProvider` exposes it. Probe
308
+ before you call, and let the SDK create accounts instead.
309
+
310
+ **Treating the returned id as confirmation.** On a fast local node a queued
311
+ transaction lands before your next line runs, so skipping `waitForTransaction`
312
+ looks correct in development and drops results in production.
313
+
314
+ **The not-installed path is never exercised in development.** Your machine has
315
+ the extension. Test the `NotDetected` branch: `connect()` opens a store page
316
+ in a new tab and throws.
317
+
318
+ ## Writing an adapter for another wallet
319
+
320
+ Extend `BaseMessageSignerWalletAdapter` from
321
+ `@miden-sdk/miden-wallet-adapter-base`. It is an `EventEmitter` over
322
+ `connect`, `disconnect`, `error` and `readyStateChange`, and requires
323
+ `name` (branded `WalletName`), `url`, `icon`, `readyState`, `address`,
324
+ `publicKey`, `connecting`, `supportedTransactionVersions`, `connect`,
325
+ `disconnect` and the full request surface. `connected` is derived from
326
+ `address`. Use `scopePollingDetectionStrategy` for injection detection: it
327
+ returns immediately without a DOM and disposes its listeners on first hit.
328
+
329
+ Emit `error` with a `WalletError` subclass before rethrowing, so a provider's
330
+ `onError` sees a typed failure. Then run the conformance suite from
331
+ `@miden-sdk/miden-wallet-adapter-miden` against your real provider object, not
332
+ against a mock: `getSurfaceCases` needs no connection and catches a missing
333
+ method, `getBehaviorCases` needs a live connected provider and checks the
334
+ response envelopes. Assert `CONFORMANCE_BUILD.real` first so a mocked module
335
+ cannot make the run pass vacuously.