@nockchain/rose 0.1.4-nightly.5 → 0.1.4-nightly.6

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.
@@ -0,0 +1,141 @@
1
+ # Provider Compatibility Guide
2
+
3
+ Rose wallet implements [EIP-6963: Multi Injected Provider Discovery](https://eips.ethereum.org/EIPS/eip-6963) for multi-wallet support.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import { NockchainProvider } from '@nockchain/sdk';
9
+
10
+ // Simple approach
11
+ const nockchain = new NockchainProvider();
12
+ await nockchain.connect();
13
+
14
+ // Wait for provider (recommended)
15
+ const found = await NockchainProvider.waitForInstallation(3000);
16
+ if (found) {
17
+ const nockchain = new NockchainProvider();
18
+ await nockchain.connect();
19
+ }
20
+ ```
21
+
22
+ ## How It Works
23
+
24
+ Rose uses **EIP-6963 events** instead of global namespaces:
25
+
26
+ 1. Rose announces itself via `eip6963:announceProvider` event
27
+ 2. dApps request providers via `eip6963:requestProvider` event
28
+ 3. No `window.nockchain` pollution - pure event-based discovery
29
+
30
+ **Benefits:**
31
+
32
+ - ✅ No race conditions or namespace collisions
33
+ - ✅ Multiple wallets coexist peacefully
34
+ - ✅ Industry standard (Ethereum EIP-6963)
35
+
36
+ ## Discovery Methods
37
+
38
+ ```typescript
39
+ // Check if installed (synchronous)
40
+ if (NockchainProvider.isInstalled()) {
41
+ const nockchain = new NockchainProvider();
42
+ }
43
+
44
+ // Wait for provider (async, handles race conditions)
45
+ const found = await NockchainProvider.waitForInstallation(3000);
46
+
47
+ // Discover all providers
48
+ const providers = NockchainProvider.discoverProviders();
49
+ providers.forEach(({ info, provider }) => {
50
+ console.log(info.name, info.rdns);
51
+ });
52
+ ```
53
+
54
+ ## Manual Event Listening
55
+
56
+ ```typescript
57
+ window.addEventListener('eip6963:announceProvider', event => {
58
+ const { info, provider } = event.detail;
59
+ if (info.rdns === 'net.nockchain.rose') {
60
+ // Use Rose provider
61
+ }
62
+ });
63
+
64
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
65
+ ```
66
+
67
+ ## Building a Wallet Extension
68
+
69
+ ```typescript
70
+ // Your inpage script
71
+ class YourProvider {
72
+ request(args) {
73
+ /* EIP-1193 implementation */
74
+ }
75
+ }
76
+
77
+ const provider = new YourProvider();
78
+ Object.freeze(provider);
79
+
80
+ const info = {
81
+ uuid: crypto.randomUUID(),
82
+ name: 'Your Wallet',
83
+ icon: 'data:image/svg+xml,...', // 96x96px
84
+ rdns: 'com.example.wallet',
85
+ };
86
+
87
+ function announceProvider() {
88
+ window.dispatchEvent(
89
+ new CustomEvent('eip6963:announceProvider', {
90
+ detail: Object.freeze({ info, provider }),
91
+ })
92
+ );
93
+ }
94
+
95
+ announceProvider();
96
+ window.addEventListener('eip6963:requestProvider', announceProvider);
97
+ ```
98
+
99
+ ## Complete Example
100
+
101
+ ```typescript
102
+ import { NockchainProvider, WalletNotInstalledError } from '@nockchain/sdk';
103
+
104
+ async function connectWallet() {
105
+ try {
106
+ const found = await NockchainProvider.waitForInstallation(3000);
107
+ if (!found) {
108
+ alert('Please install Rose wallet');
109
+ return;
110
+ }
111
+
112
+ const nockchain = new NockchainProvider();
113
+ const { pkh } = await nockchain.connect();
114
+
115
+ const tx = nockchain.transaction().to('recipient_address').amount(1_000_000).build();
116
+
117
+ const txId = await nockchain.sendTransaction(tx);
118
+ console.log('Transaction sent:', txId);
119
+ } catch (error) {
120
+ console.error('Connection failed:', error);
121
+ }
122
+ }
123
+ ```
124
+
125
+ ## Troubleshooting
126
+
127
+ **Provider not found?**
128
+
129
+ - Ensure Rose extension is installed and enabled
130
+ - Refresh the page after installing/reloading extension
131
+ - Use `waitForInstallation()` for race conditions
132
+
133
+ **After extension reload:**
134
+
135
+ - Always refresh the dApp page
136
+ - Inpage script only injects once per page load
137
+
138
+ ## Resources
139
+
140
+ - [EIP-6963 Specification](https://eips.ethereum.org/EIPS/eip-6963)
141
+ - [Nockchain Documentation](https://nockchain.org)
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Inpage Provider: Injected into web pages
3
- * Exposes window.nockchain with EIP-1193-style API
3
+ * Implements EIP-6963 Multi Injected Provider Discovery
4
+ * and EIP-1193 Provider API
4
5
  *
5
6
  * NOTE: This file runs in the MAIN world and cannot use any imports or Chrome APIs
6
7
  */
@@ -11,12 +12,33 @@ import { version } from '../../package.json';
11
12
  // Inline constant to avoid imports
12
13
  const MESSAGE_TARGET = 'ROSE';
13
14
 
15
+ // EIP-6963 types (inline to avoid circular dependencies)
16
+ interface EIP6963ProviderInfo {
17
+ uuid: string;
18
+ name: string;
19
+ icon: string;
20
+ rdns: string;
21
+ }
22
+
23
+ interface EIP6963ProviderDetail {
24
+ info: EIP6963ProviderInfo;
25
+ provider: InjectedNockchain;
26
+ }
27
+
28
+ // EIP-6963 Provider Info for Rose Wallet
29
+ const PROVIDER_INFO: EIP6963ProviderInfo = {
30
+ uuid: '350670db-19fa-4704-a166-e52e178b59d2',
31
+ name: 'Rose',
32
+ icon: 'data:image/svg+xml,%3Csvg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2120 2120" width="96" height="96"%3E%3Cstyle%3E.s0%7Bfill:%23ec1e24%7D.s1%7Bfill:%23e51d25%7D.s2%7Bfill:%230f8945%7D.s3%7Bfill:%230a7e40%7D%3C/style%3E%3Cg id="Layer 1"%3E%3Cg%3E%3Cpath class="s0" d="m1141.12 574.61l-85.01 53.22c-20.3 10.36-40.1 22.84-60.16 30.45-49.08 18.59-96.03 40.49-147.89 59.18-54.93 18.09-54.19 13.65-66.34 49.66-6.4 17.8-18.35 39.63-17.9 58.38-12.06 66.85-20.4 114.97 3.28 189.1 2.69 8.89 3.96 14.2 8.36 19.43 2.58 6.12 7.4 14.5 10.07 19.59l14.59 23.44c4.02 5.55 3.11 4.08 6.38 10.26 2.99 5.45 4.22 7.29 8.61 12.53 25.19 33.02 7.94 56.12 129.82 68.81 61.46 6.39 80.86 6.35 136.09-7.21 8.64-3.75 15.56-7.65 25.89-10.91 9.99-3.29 16.25-7.59 25.64-11.97 40.4-18.31 72.77-51.87 100.64-85.47 22.49-27.49 69.69-96.14 83.16-127.53 3.94-9.36 3.95-13.51 8.57-22.81 40.75-82.03 73.22-220.2 71.51-315.97-0.38-35.37-1.11-62.81-4.49-95.67-1.3-12.93-4.7-29.51-7.89-44.68l-0.38-3.5q-0.68-4.22-1.53-6.37c-1.46-7.06-9.65-40.34-7.8-45.72-1.49-6.71-0.26-20.81-16.5-9.4-9.48 5.41-71.09 72.45-90.28 89.84-45.75 40.01-84.95 68.47-126.44 103.32z"/%3E%3Cpath class="s2" d="m1113.77 1171.23l-24.83 10.66c-1.68 19.26 8.4 22.9 21.15 31.64 3.26 2.36 33.95 19.93 36.3 20.83 11.39 4.45 37.71 8.48 51.06 10.33 38.19-0.83 113.88 1.25 147.03 10.38 3.38 0.98 29.68 13.32 32.28 15.28 3.29 2.02 17.7 19.56 30.35 9.57 17.75-48.69-51.06-113.92-76.47-132.42-36.48-26.73-82.55-54.65-107.35-44.34-14.36 5.68-76.04 53.67-109.52 68.07z"/%3E%3Cpath class="s3" d="m985.44 1204.75q31.91-2.42 63.82-4.83c152.67 562.41-158.23 1208.49-279.97 1584.18-47.44-16.26-63.24-21.79-110.67-38.04 147.3-327.05 482.61-955.06 326.82-1541.31z"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E',
33
+ rdns: 'net.nockchain.rose',
34
+ };
35
+
14
36
  class NockchainProvider implements InjectedNockchain {
15
37
  name: string = 'rose';
16
38
  version: string = version;
17
39
 
18
40
  /**
19
- * Make a request to the wallet
41
+ * Make a request to the wallet (EIP-1193 compatible)
20
42
  * @param args - Request arguments with method and params
21
43
  */
22
44
  request<T = unknown>(args: RpcRequest): Promise<T> {
@@ -70,17 +92,32 @@ class NockchainProvider implements InjectedNockchain {
70
92
  }
71
93
  }
72
94
 
73
- // Inject provider into window
95
+ // Create the provider instance
74
96
  const provider = new NockchainProvider();
75
- const globalNockchain = (window as any).nockchain ?? {};
76
- globalNockchain.providers = Array.isArray(globalNockchain.providers)
77
- ? globalNockchain.providers
78
- : [];
79
- if (!globalNockchain.providers.some((p: { name: string }) => p.name === provider.name)) {
80
- globalNockchain.providers.push(provider);
97
+
98
+ // Freeze the provider to prevent tampering (EIP-6963 recommendation)
99
+ Object.freeze(provider);
100
+
101
+ /**
102
+ * Announce the Rose provider using EIP-6963 events
103
+ */
104
+ function announceProvider() {
105
+ const detail: EIP6963ProviderDetail = Object.freeze({
106
+ info: PROVIDER_INFO,
107
+ provider: provider,
108
+ });
109
+
110
+ window.dispatchEvent(
111
+ new CustomEvent('eip6963:announceProvider', {
112
+ detail,
113
+ })
114
+ );
81
115
  }
82
- // Preserve existing global while registering this provider
83
- (window as any).nockchain = globalNockchain;
84
116
 
85
- // Announce provider availability
86
- window.dispatchEvent(new Event(`nockchain_${provider.name}#initialized`));
117
+ // Announce immediately on load
118
+ announceProvider();
119
+
120
+ // Listen for provider requests and re-announce (EIP-6963 pattern)
121
+ window.addEventListener('eip6963:requestProvider', () => {
122
+ announceProvider();
123
+ });
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "Rose Wallet",
4
4
  "homepage_url": "https://nockchain.net",
5
- "version": "0.1.4.5",
5
+ "version": "0.1.4.6",
6
6
  "description": "Rose Wallet - Browser Wallet for Nockchain",
7
7
  "icons": {
8
8
  "16": "icons/rose16.png",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nockchain/rose",
3
- "version": "0.1.4-nightly.5",
3
+ "version": "0.1.4-nightly.6",
4
4
  "description": "Rose - Chrome Wallet Extension for Nockchain",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@fontsource/inter": "5.2.8",
16
16
  "@fontsource/lora": "5.2.8",
17
- "@nockchain/sdk": "0.1.4-nightly.5",
17
+ "@nockchain/sdk": "0.1.4-nightly.6",
18
18
  "@scure/base": "2.0.0",
19
19
  "@scure/bip39": "2.0.1",
20
20
  "react": "19.2.3",
package/sdk/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nockchain/sdk",
3
- "version": "0.1.4-nightly.5",
3
+ "version": "0.1.4-nightly.6",
4
4
  "description": "TypeScript SDK for interacting with Nockchain wallet extensions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * NockchainProvider - Main SDK class for interacting with Rose wallet
3
+ * Supports EIP-6963 Multi Injected Provider Discovery
3
4
  */
4
5
 
5
- import type { Transaction, NockchainEvent, EventListener, InjectedNockchain } from './types.js';
6
+ import type {
7
+ Transaction,
8
+ NockchainEvent,
9
+ EventListener,
10
+ InjectedNockchain,
11
+ EIP6963ProviderDetail,
12
+ EIP6963AnnounceProviderEvent,
13
+ } from './types.js';
6
14
  import { TransactionBuilder } from './transaction.js';
7
15
  import { WalletNotInstalledError, UserRejectedError, RpcError, NoAccountError } from './errors.js';
8
16
  import { PROVIDER_METHODS } from './constants.js';
@@ -42,19 +50,14 @@ export class NockchainProvider {
42
50
  throw new Error('NockchainProvider can only be used in a browser environment');
43
51
  }
44
52
 
45
- // Verify Rose extension is installed and authentic
46
- if (!NockchainProvider.isInstalled()) {
47
- throw new WalletNotInstalledError();
48
- }
49
-
50
- const injected = window.nockchain;
53
+ // Discover Rose provider using EIP-6963
54
+ const eip6963Provider = NockchainProvider.discoverEIP6963Provider();
51
55
 
52
- // TODO: remove this duplicate check
53
- if (!injected) {
56
+ if (!eip6963Provider) {
54
57
  throw new WalletNotInstalledError();
55
58
  }
56
59
 
57
- this.injected = injected;
60
+ this.injected = eip6963Provider;
58
61
  this.eventListeners = new Map();
59
62
 
60
63
  // Initialize event listeners for wallet events
@@ -381,16 +384,126 @@ export class NockchainProvider {
381
384
  }
382
385
  }
383
386
 
387
+ /**
388
+ * Discover Rose provider using EIP-6963 (preferred method)
389
+ * @returns The Rose provider if found, null otherwise
390
+ * @private
391
+ */
392
+ private static discoverEIP6963Provider(): InjectedNockchain | null {
393
+ if (typeof window === 'undefined') {
394
+ return null;
395
+ }
396
+
397
+ let roseProvider: InjectedNockchain | null = null;
398
+
399
+ // Synchronous event listener - must be added before dispatching
400
+ const handler = (event: Event) => {
401
+ const announceEvent = event as EIP6963AnnounceProviderEvent;
402
+ if (announceEvent.detail?.info?.rdns === 'net.nockchain.rose') {
403
+ roseProvider = announceEvent.detail.provider;
404
+ }
405
+ };
406
+
407
+ // Add listener synchronously
408
+ window.addEventListener('eip6963:announceProvider', handler);
409
+
410
+ // Request providers to announce themselves
411
+ // Providers that are already loaded will respond synchronously
412
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
413
+
414
+ // Clean up listener immediately (synchronous announcements already happened)
415
+ window.removeEventListener('eip6963:announceProvider', handler);
416
+
417
+ return roseProvider;
418
+ }
419
+
420
+ /**
421
+ * Discover all available Nockchain providers using EIP-6963
422
+ * @returns Array of provider details
423
+ */
424
+ static discoverProviders(): EIP6963ProviderDetail[] {
425
+ if (typeof window === 'undefined') {
426
+ return [];
427
+ }
428
+
429
+ const providers: EIP6963ProviderDetail[] = [];
430
+
431
+ const handler = (event: Event) => {
432
+ const announceEvent = event as EIP6963AnnounceProviderEvent;
433
+ if (announceEvent.detail) {
434
+ providers.push(announceEvent.detail);
435
+ }
436
+ };
437
+
438
+ window.addEventListener('eip6963:announceProvider', handler);
439
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
440
+ window.removeEventListener('eip6963:announceProvider', handler);
441
+
442
+ return providers;
443
+ }
444
+
384
445
  /**
385
446
  * Check if the Rose extension is installed and authentic
386
- * @returns true if the extension is installed
447
+ * Uses EIP-6963 discovery (synchronous check)
448
+ *
449
+ * Note: This performs synchronous discovery which only works if the provider
450
+ * has already announced itself. For async discovery, use waitForInstallation().
451
+ *
452
+ * @returns true if the extension is installed and has announced itself
387
453
  */
388
454
  static isInstalled(): boolean {
389
- if (typeof window === 'undefined' || !window.nockchain?.providers) {
455
+ if (typeof window === 'undefined') {
390
456
  return false;
391
457
  }
392
- return window.nockchain.providers.some(
393
- (provider: { name?: string }) => provider && provider.name === 'rose'
394
- );
458
+
459
+ const eip6963Provider = NockchainProvider.discoverEIP6963Provider();
460
+ return eip6963Provider !== null;
461
+ }
462
+
463
+ /**
464
+ * Wait for the Rose extension to be available
465
+ * Useful if the extension might load after your script
466
+ * Uses EIP-6963 discovery
467
+ * @param timeout - Maximum time to wait in milliseconds (default: 3000)
468
+ * @returns Promise that resolves to true if found, false if timeout
469
+ */
470
+ static async waitForInstallation(timeout = 3000): Promise<boolean> {
471
+ if (NockchainProvider.isInstalled()) {
472
+ return true;
473
+ }
474
+
475
+ return new Promise(resolve => {
476
+ const startTime = Date.now();
477
+ let resolved = false;
478
+
479
+ const checkAndResolve = (found: boolean) => {
480
+ if (!resolved) {
481
+ resolved = true;
482
+ clearInterval(checkInterval);
483
+ window.removeEventListener('eip6963:announceProvider', eip6963Handler);
484
+ resolve(found);
485
+ }
486
+ };
487
+
488
+ const checkInterval = setInterval(() => {
489
+ if (NockchainProvider.isInstalled()) {
490
+ checkAndResolve(true);
491
+ } else if (Date.now() - startTime > timeout) {
492
+ checkAndResolve(false);
493
+ }
494
+ }, 100);
495
+
496
+ // Listen for EIP-6963 announcements
497
+ const eip6963Handler = (event: Event) => {
498
+ const announceEvent = event as EIP6963AnnounceProviderEvent;
499
+ if (announceEvent.detail?.info?.rdns === 'net.nockchain.rose') {
500
+ checkAndResolve(true);
501
+ }
502
+ };
503
+ window.addEventListener('eip6963:announceProvider', eip6963Handler);
504
+
505
+ // Request providers to announce themselves
506
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
507
+ });
395
508
  }
396
509
  }
package/sdk/src/types.ts CHANGED
@@ -51,7 +51,7 @@ export type NockchainEvent = 'accountsChanged' | 'chainChanged' | 'connect' | 'd
51
51
  export type EventListener<T = unknown> = (data: T) => void;
52
52
 
53
53
  /**
54
- * Interface for the injected window.nockchain object
54
+ * Interface for the injected window.nockchain object (EIP-1193 compatible)
55
55
  */
56
56
  export interface InjectedNockchain {
57
57
  /**
@@ -62,18 +62,53 @@ export interface InjectedNockchain {
62
62
  request<T = unknown>(request: RpcRequest): Promise<T>;
63
63
 
64
64
  /**
65
- * Provider names and versions (e.g., 'rose', 'iris')
65
+ * Provider name (e.g., 'rose', 'iris')
66
66
  */
67
- providers?: { name: string; version: string }[];
67
+ name?: string;
68
+
69
+ /**
70
+ * Provider version
71
+ */
72
+ version?: string;
73
+ }
74
+
75
+ /**
76
+ * EIP-6963 Provider Info
77
+ * Metadata about the wallet provider
78
+ */
79
+ export interface EIP6963ProviderInfo {
80
+ /** Globally unique identifier (UUIDv4) */
81
+ uuid: string;
82
+ /** Human-readable name of the wallet */
83
+ name: string;
84
+ /** Data URI for the wallet icon (96x96px minimum) */
85
+ icon: string;
86
+ /** Reverse DNS identifier (e.g., 'net.nockchain.rose') */
87
+ rdns: string;
88
+ }
89
+
90
+ /**
91
+ * EIP-6963 Provider Detail
92
+ * Combined provider info and EIP-1193 provider
93
+ */
94
+ export interface EIP6963ProviderDetail {
95
+ info: EIP6963ProviderInfo;
96
+ provider: InjectedNockchain;
97
+ }
98
+
99
+ /**
100
+ * EIP-6963 Announce Provider Event
101
+ */
102
+ export interface EIP6963AnnounceProviderEvent extends CustomEvent {
103
+ type: 'eip6963:announceProvider';
104
+ detail: EIP6963ProviderDetail;
68
105
  }
69
106
 
70
107
  /**
71
- * Extended Window interface with nockchain property
108
+ * EIP-6963 Request Provider Event
72
109
  */
73
- declare global {
74
- interface Window {
75
- nockchain?: InjectedNockchain;
76
- }
110
+ export interface EIP6963RequestProviderEvent extends Event {
111
+ type: 'eip6963:requestProvider';
77
112
  }
78
113
 
79
114
  /**
Binary file