applesauce-wallet-connect 0.0.0-next-20250923113611 → 0.0.0-next-20251009073624

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 CHANGED
@@ -41,10 +41,10 @@ Create a wallet service that handles NIP-47 requests:
41
41
 
42
42
  ```typescript
43
43
  import { WalletService } from "applesauce-wallet-connect";
44
- import { SimpleSigner } from "applesauce-signers";
44
+ import { PrivateKeySigner } from "applesauce-signers";
45
45
 
46
46
  // Create a signer for the service
47
- const signer = new SimpleSigner();
47
+ const signer = new PrivateKeySigner();
48
48
 
49
49
  // Define method handlers
50
50
  const handlers = {
@@ -1,4 +1,9 @@
1
1
  import { EventBlueprint } from "applesauce-factory";
2
2
  import { WalletSupport } from "../helpers/support.js";
3
- /** Creates a wallet info event */
4
- export declare function WalletSupportBlueprint(info: WalletSupport, client?: string): EventBlueprint;
3
+ /**
4
+ * Creates a wallet info event
5
+ * @param info - The wallet support information
6
+ * @param client - The client pubkey
7
+ * @param overrideRelay - An optional relay to tell the client which relay to use (for nostr+walletauth URI connections)
8
+ */
9
+ export declare function WalletSupportBlueprint(info: WalletSupport, client?: string, overrideRelay?: string): EventBlueprint;
@@ -2,9 +2,14 @@ import { blueprint } from "applesauce-factory";
2
2
  import { setContent } from "applesauce-factory/operations/content";
3
3
  import { includeSingletonTag } from "applesauce-factory/operations";
4
4
  import { WALLET_INFO_KIND } from "../helpers/support.js";
5
- /** Creates a wallet info event */
6
- export function WalletSupportBlueprint(info, client) {
5
+ /**
6
+ * Creates a wallet info event
7
+ * @param info - The wallet support information
8
+ * @param client - The client pubkey
9
+ * @param overrideRelay - An optional relay to tell the client which relay to use (for nostr+walletauth URI connections)
10
+ */
11
+ export function WalletSupportBlueprint(info, client, overrideRelay) {
7
12
  return blueprint(WALLET_INFO_KIND, setContent(info.methods.join(" ")), info.encryption ? includeSingletonTag(["encryption", info.encryption.join(" ")]) : undefined, info.notifications ? includeSingletonTag(["notifications", info.notifications.join(" ")]) : undefined,
8
13
  // An optional client pubkey to notify the service is created (used for nostr+walletauth URI connections)
9
- client ? includeSingletonTag(["p", client]) : undefined);
14
+ client ? includeSingletonTag(overrideRelay ? ["p", client, overrideRelay] : ["p", client]) : undefined);
10
15
  }
@@ -14,6 +14,8 @@ export type WalletConnectOptions = NostrConnectionMethodsOptions & {
14
14
  service?: string;
15
15
  /** Default timeout for RPC requests in milliseconds */
16
16
  timeout?: number;
17
+ /** Whether to accept the relay hint from the wallet service */
18
+ acceptRelayHint?: boolean;
17
19
  };
18
20
  export declare class WalletConnect {
19
21
  /** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
@@ -30,7 +32,10 @@ export declare class WalletConnect {
30
32
  readonly secret: Uint8Array;
31
33
  protected readonly signer: EventSigner;
32
34
  /** The relays to use for the connection */
33
- readonly relays: string[];
35
+ protected relays$: BehaviorSubject<string[]>;
36
+ get relays(): string[];
37
+ /** Whether to accept the relay hint from the wallet service */
38
+ acceptRelayHint: boolean;
34
39
  /** The wallet service public key ( unset if waiting for service ) */
35
40
  service$: BehaviorSubject<string | undefined>;
36
41
  get service(): string | undefined;
@@ -2,7 +2,7 @@ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
2
2
  import { simpleTimeout } from "applesauce-core";
3
3
  import { create } from "applesauce-factory";
4
4
  import { finalizeEvent, getPublicKey, nip04, nip44, verifyEvent } from "nostr-tools";
5
- import { BehaviorSubject, defer, filter, firstValueFrom, from, fromEvent, identity, ignoreElements, lastValueFrom, map, merge, mergeMap, repeat, ReplaySubject, retry, share, switchMap, takeUntil, tap, timer, toArray, } from "rxjs";
5
+ import { BehaviorSubject, combineLatest, defer, filter, firstValueFrom, from, fromEvent, identity, ignoreElements, lastValueFrom, map, merge, mergeMap, of, repeat, ReplaySubject, retry, share, switchMap, take, takeUntil, tap, timer, toArray, } from "rxjs";
6
6
  import { WalletRequestBlueprint } from "./blueprints/index.js";
7
7
  import { createWalletError } from "./helpers/error.js";
8
8
  import { createWalletAuthURI, getPreferredEncryption, getWalletRequestEncryption, getWalletResponseRequestId, getWalletSupport, isValidWalletNotification, isValidWalletResponse, parseWalletConnectURI, supportsMethod, supportsNotifications, supportsNotificationType, unlockWalletNotification, unlockWalletResponse, WALLET_INFO_KIND, WALLET_LEGACY_NOTIFICATION_KIND, WALLET_NOTIFICATION_KIND, WALLET_RESPONSE_KIND, } from "./helpers/index.js";
@@ -22,7 +22,12 @@ export class WalletConnect {
22
22
  secret;
23
23
  signer;
24
24
  /** The relays to use for the connection */
25
- relays;
25
+ relays$ = new BehaviorSubject([]);
26
+ get relays() {
27
+ return this.relays$.value;
28
+ }
29
+ /** Whether to accept the relay hint from the wallet service */
30
+ acceptRelayHint;
26
31
  /** The wallet service public key ( unset if waiting for service ) */
27
32
  service$ = new BehaviorSubject(undefined);
28
33
  get service() {
@@ -42,7 +47,8 @@ export class WalletConnect {
42
47
  waitForService$;
43
48
  constructor(options) {
44
49
  this.secret = options.secret;
45
- this.relays = options.relays;
50
+ this.relays$.next(options.relays);
51
+ this.acceptRelayHint = options.acceptRelayHint ?? true;
46
52
  this.service$.next(options.service);
47
53
  this.defaultTimeout = options.timeout || 30000; // 30 second default timeout
48
54
  // Create a signer for the factory
@@ -64,18 +70,18 @@ export class WalletConnect {
64
70
  this.subscriptionMethod = (relays, filters) => subscriptionMethod(relays, filters);
65
71
  this.publishMethod = (relays, event) => publishMethod(relays, event);
66
72
  // Create shared observable for all wallet events
67
- this.events$ = this.service$.pipe(switchMap((service) => {
73
+ this.events$ = combineLatest([this.service$, this.relays$]).pipe(switchMap(([service, relays]) => {
68
74
  const client = getPublicKey(this.secret);
69
75
  // If the service is not known yet, subscribe to a wallet info event tagging the client
70
76
  if (!service)
71
- return from(this.subscriptionMethod(this.relays, [{ kinds: [WALLET_INFO_KIND], "#p": [client] }])).pipe(
77
+ return from(this.subscriptionMethod(relays, [{ kinds: [WALLET_INFO_KIND], "#p": [client] }])).pipe(
72
78
  // Keep the connection open indefinitely
73
79
  repeat(),
74
80
  // Retry on connection failure
75
81
  retry(),
76
82
  // Ignore strings (support for applesauce-relay)
77
83
  filter((event) => typeof event !== "string"));
78
- return from(this.subscriptionMethod(this.relays, [
84
+ return from(this.subscriptionMethod(relays, [
79
85
  // Subscribe to response events
80
86
  {
81
87
  kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
@@ -104,18 +110,29 @@ export class WalletConnect {
104
110
  }));
105
111
  this.encryption$ = this.support$.pipe(map((info) => (info ? getPreferredEncryption(info) : "nip04")));
106
112
  this.notifications$ = this.events$.pipe(filter((event) => isValidWalletNotification(event)), mergeMap((event) => this.handleNotificationEvent(event)));
107
- this.waitForService$ = this.events$.pipe(
108
- // Complete when the service is set
109
- takeUntil(this.service$),
110
- // Only listen for wallet info events
111
- filter((event) => event.kind === WALLET_INFO_KIND && !this.service),
112
- // Set the service to the pubkey of the wallet info event
113
- tap((event) => {
114
- // Set the service to the pubkey of the wallet info event
115
- this.service$.next(event.pubkey);
116
- }),
117
- // Get the service pubkey from the event
118
- map((event) => event.pubkey),
113
+ this.waitForService$ = defer(() =>
114
+ // If service is already set, return it
115
+ this.service$.value
116
+ ? of(this.service$.value)
117
+ : // Otherwise listen for new wallet info events
118
+ this.events$.pipe(
119
+ // Only listen for wallet info events
120
+ filter((event) => event.kind === WALLET_INFO_KIND),
121
+ // Set the service to the pubkey of the wallet info event
122
+ tap((event) => {
123
+ // Set the service to the pubkey of the wallet info event
124
+ this.service$.next(event.pubkey);
125
+ // Switch to the relay from the service if its set
126
+ if (this.acceptRelayHint) {
127
+ const relay = event.tags.find((t) => t[0] === "p" && t[2])?.[2];
128
+ if (relay)
129
+ this.relays$.next([relay]);
130
+ }
131
+ }),
132
+ // Get the service pubkey from the event
133
+ map((event) => event.pubkey),
134
+ // Complete after the first value
135
+ take(1))).pipe(
119
136
  // Only create a single subscription to avoid multiple side effects
120
137
  share());
121
138
  }
@@ -144,7 +161,7 @@ export class WalletConnect {
144
161
  request(request, options = {}) {
145
162
  if (!this.service)
146
163
  throw new Error("WalletConnect is not connected to a service");
147
- // Create the request evnet
164
+ // Create the request event
148
165
  return defer(async () => {
149
166
  // Get the preferred encryption method for the wallet
150
167
  const encryption = await firstValueFrom(this.encryption$);
@@ -82,7 +82,7 @@ export declare class WalletService {
82
82
  /** The service's public key */
83
83
  pubkey: string | null;
84
84
  /** The client's secret key */
85
- protected secret: Uint8Array;
85
+ protected secret?: Uint8Array;
86
86
  /** The client's public key */
87
87
  client: string;
88
88
  /** Shared observable for all wallet request events */
@@ -115,5 +115,8 @@ export declare class WalletService {
115
115
  /** Send a response event */
116
116
  protected sendResponse(requestEvent: WalletRequestEvent, response: WalletResponse): Promise<void>;
117
117
  /** Creates a service for a nostr+walletauth URI */
118
- static fromAuthURI(uri: string | WalletAuthURI, options: Omit<WalletServiceOptions, "relays">): WalletService;
118
+ static fromAuthURI(uri: string | WalletAuthURI, options: Omit<WalletServiceOptions, "relays"> & {
119
+ /** A relay or method to select a single relay for the client and service to communicate over */
120
+ overrideRelay?: string | ((relays: string[]) => string);
121
+ }): WalletService;
119
122
  }
@@ -49,10 +49,16 @@ export class WalletService {
49
49
  this.handlers = options.handlers;
50
50
  // Set the client's secret and public key
51
51
  if (options.secret) {
52
+ // Service was created with a custom secret
52
53
  this.secret = options.secret;
53
54
  this.client = getPublicKey(this.secret);
54
55
  }
56
+ else if (options.client) {
57
+ // Service was restored with only the clients pubkey
58
+ this.client = options.client;
59
+ }
55
60
  else {
61
+ // Generate secret and client pubkey
56
62
  this.secret = generateSecretKey();
57
63
  this.client = getPublicKey(this.secret);
58
64
  }
@@ -131,6 +137,8 @@ export class WalletService {
131
137
  }
132
138
  /** Get the connection URI for the service */
133
139
  getConnectURI() {
140
+ if (!this.secret)
141
+ throw new Error("Service was not created with a secret");
134
142
  if (!this.pubkey)
135
143
  throw new Error("Service is not running");
136
144
  if (!this.relays.length)
@@ -154,7 +162,9 @@ export class WalletService {
154
162
  /** Publish the wallet support event */
155
163
  async publishSupportEvent() {
156
164
  try {
157
- const draft = await create({ signer: this.signer }, WalletSupportBlueprint, this.support, this.client);
165
+ // Tell the client which relay to use if there is only one (for nostr+walletauth URI connections)
166
+ const overrideRelay = this.relays.length === 1 ? this.relays[0] : undefined;
167
+ const draft = await create({ signer: this.signer }, WalletSupportBlueprint, this.support, this.client, overrideRelay);
158
168
  const event = await this.signer.signEvent(draft);
159
169
  await this.publishMethod(this.relays, event);
160
170
  }
@@ -276,10 +286,13 @@ export class WalletService {
276
286
  }
277
287
  /** Creates a service for a nostr+walletauth URI */
278
288
  static fromAuthURI(uri, options) {
279
- const { client, relays } = typeof uri === "string" ? parseWalletAuthURI(uri) : uri;
289
+ const authURI = typeof uri === "string" ? parseWalletAuthURI(uri) : uri;
290
+ const relays = options.overrideRelay
291
+ ? [typeof options.overrideRelay === "function" ? options.overrideRelay(authURI.relays) : options.overrideRelay]
292
+ : authURI.relays;
280
293
  return new WalletService({
281
294
  ...options,
282
- client,
295
+ client: authURI.client,
283
296
  relays,
284
297
  });
285
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-wallet-connect",
3
- "version": "0.0.0-next-20250923113611",
3
+ "version": "0.0.0-next-20251009073624",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -59,8 +59,8 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@noble/hashes": "^1.7.1",
62
- "applesauce-core": "0.0.0-next-20250923113611",
63
- "applesauce-factory": "0.0.0-next-20250923113611",
62
+ "applesauce-core": "^4.0.0",
63
+ "applesauce-factory": "^4.0.0",
64
64
  "nostr-tools": "~2.17",
65
65
  "rxjs": "^7.8.1"
66
66
  },