applesauce-wallet-connect 0.0.0-next-20250808173123 → 0.0.0-next-20250828144630

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.
Files changed (79) hide show
  1. package/README.md +3 -3
  2. package/dist/blueprints/support.d.ts +1 -1
  3. package/dist/blueprints/support.js +4 -2
  4. package/dist/helpers/auth-uri.d.ts +44 -0
  5. package/dist/helpers/auth-uri.js +113 -0
  6. package/dist/helpers/connect-uri.d.ts +5 -0
  7. package/dist/helpers/connect-uri.js +6 -2
  8. package/dist/helpers/index.d.ts +1 -0
  9. package/dist/helpers/index.js +1 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/interop.d.ts +35 -0
  13. package/dist/interop.js +19 -0
  14. package/dist/types.d.ts +5 -0
  15. package/dist/wallet-connect.d.ts +25 -14
  16. package/dist/wallet-connect.js +95 -34
  17. package/dist/wallet-service.d.ts +19 -10
  18. package/dist/wallet-service.js +40 -20
  19. package/package.json +4 -4
  20. package/dist/actions/index.d.ts +0 -1
  21. package/dist/actions/index.js +0 -1
  22. package/dist/actions/tokens.d.ts +0 -17
  23. package/dist/actions/tokens.js +0 -110
  24. package/dist/actions/wallet.d.ts +0 -13
  25. package/dist/actions/wallet.js +0 -64
  26. package/dist/actions/zap-info.d.ts +0 -22
  27. package/dist/actions/zap-info.js +0 -83
  28. package/dist/actions/zaps.d.ts +0 -8
  29. package/dist/actions/zaps.js +0 -30
  30. package/dist/blueprints/history.d.ts +0 -4
  31. package/dist/blueprints/history.js +0 -11
  32. package/dist/blueprints/info.d.ts +0 -4
  33. package/dist/blueprints/info.js +0 -8
  34. package/dist/blueprints/tokens.d.ts +0 -7
  35. package/dist/blueprints/tokens.js +0 -11
  36. package/dist/blueprints/wallet.d.ts +0 -5
  37. package/dist/blueprints/wallet.js +0 -11
  38. package/dist/blueprints/zaps.d.ts +0 -8
  39. package/dist/blueprints/zaps.js +0 -12
  40. package/dist/helpers/animated-qr.d.ts +0 -30
  41. package/dist/helpers/animated-qr.js +0 -71
  42. package/dist/helpers/history.d.ts +0 -26
  43. package/dist/helpers/history.js +0 -47
  44. package/dist/helpers/info.d.ts +0 -34
  45. package/dist/helpers/info.js +0 -97
  46. package/dist/helpers/methods.d.ts +0 -1
  47. package/dist/helpers/methods.js +0 -1
  48. package/dist/helpers/nutzap.d.ts +0 -27
  49. package/dist/helpers/nutzap.js +0 -66
  50. package/dist/helpers/tokens.d.ts +0 -58
  51. package/dist/helpers/tokens.js +0 -162
  52. package/dist/helpers/wallet.d.ts +0 -15
  53. package/dist/helpers/wallet.js +0 -41
  54. package/dist/helpers/zap-info.d.ts +0 -19
  55. package/dist/helpers/zap-info.js +0 -42
  56. package/dist/interface.d.ts +0 -6
  57. package/dist/interface.js +0 -1
  58. package/dist/models/history.d.ts +0 -6
  59. package/dist/models/history.js +0 -21
  60. package/dist/models/index.d.ts +0 -4
  61. package/dist/models/index.js +0 -4
  62. package/dist/models/nutzap.d.ts +0 -6
  63. package/dist/models/nutzap.js +0 -16
  64. package/dist/models/tokens.d.ts +0 -6
  65. package/dist/models/tokens.js +0 -58
  66. package/dist/models/wallet.d.ts +0 -13
  67. package/dist/models/wallet.js +0 -18
  68. package/dist/operations/history.d.ts +0 -7
  69. package/dist/operations/history.js +0 -34
  70. package/dist/operations/index.d.ts +0 -5
  71. package/dist/operations/index.js +0 -5
  72. package/dist/operations/nutzap.d.ts +0 -14
  73. package/dist/operations/nutzap.js +0 -33
  74. package/dist/operations/tokens.d.ts +0 -4
  75. package/dist/operations/tokens.js +0 -24
  76. package/dist/operations/wallet.d.ts +0 -8
  77. package/dist/operations/wallet.js +0 -30
  78. package/dist/operations/zap-info.d.ts +0 -10
  79. package/dist/operations/zap-info.js +0 -17
package/README.md CHANGED
@@ -24,7 +24,7 @@ Connect to a wallet service using a connection string:
24
24
  ```typescript
25
25
  import { WalletConnect } from "applesauce-wallet-connect";
26
26
 
27
- const wallet = WalletConnect.fromConnectionString("nostr+walletconnect://relay.example.com?secret=...&pubkey=...");
27
+ const wallet = WalletConnect.fromConnectURI("nostr+walletconnect://relay.example.com?secret=...&pubkey=...");
28
28
 
29
29
  // Pay an invoice
30
30
  const result = await wallet.payInvoice("lnbc1...");
@@ -87,10 +87,10 @@ await service.start();
87
87
  console.log("Wallet service started");
88
88
 
89
89
  // Get the connection string for the wallet service
90
- console.log(service.getConnectionString());
90
+ console.log(service.getConnectURI());
91
91
 
92
92
  // Stop the service when done
93
- // service.stop();
93
+ service.stop();
94
94
  ```
95
95
 
96
96
  ## Supported Methods
@@ -1,4 +1,4 @@
1
1
  import { EventBlueprint } from "applesauce-factory";
2
2
  import { WalletSupport } from "../helpers/support.js";
3
3
  /** Creates a wallet info event */
4
- export declare function WalletSupportBlueprint(info: WalletSupport): EventBlueprint;
4
+ export declare function WalletSupportBlueprint(info: WalletSupport, client?: string): EventBlueprint;
@@ -3,6 +3,8 @@ 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
5
  /** Creates a wallet info event */
6
- export function WalletSupportBlueprint(info) {
7
- 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);
6
+ export function WalletSupportBlueprint(info, client) {
7
+ 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
+ // An optional client pubkey to notify the service is created (used for nostr+walletauth URI connections)
9
+ client ? includeSingletonTag(["p", client]) : undefined);
8
10
  }
@@ -0,0 +1,44 @@
1
+ import { NotificationType } from "./notification.js";
2
+ import { WalletMethod } from "./support.js";
3
+ export interface WalletAuthURI {
4
+ /** The public key of the client requesting authorization */
5
+ client: string;
6
+ /** Required. URL of the relay where the client intends to communicate with the wallet service */
7
+ relays: string[];
8
+ /** The name of the client app (optional) */
9
+ name?: string;
10
+ /** The URL of an icon of the client app to display on the confirmation page (optional) */
11
+ icon?: string;
12
+ /** URI to open after the connection is created (optional) */
13
+ returnTo?: string;
14
+ /** The connection cannot be used after this date. Unix timestamp in seconds (optional) */
15
+ expiresAt?: number;
16
+ /** The maximum amount in millisats that can be sent per renewal period (optional) */
17
+ maxAmount?: number;
18
+ /** The reset the budget at the end of the given budget renewal. Can be never (default), daily, weekly, monthly, yearly (optional) */
19
+ budgetRenewal?: "never" | "daily" | "weekly" | "monthly" | "yearly";
20
+ /** List of request types that you need permission for (optional) */
21
+ methods?: WalletMethod[];
22
+ /** List of notification types that you need permission for (optional) */
23
+ notifications?: NotificationType[];
24
+ /** The makes an isolated app connection / sub-wallet with its own balance and only access to its own transaction list (optional) */
25
+ isolated?: boolean;
26
+ /** Url encoded, JSON-serialized metadata that describes the app connection (optional) */
27
+ metadata?: Record<string, any>;
28
+ /** The wallet name for nostr+walletauth+walletname scheme (optional) */
29
+ walletName?: string;
30
+ }
31
+ /**
32
+ * Parses a nostr+walletauth URI
33
+ * @throws {Error} if the authorization URI is invalid
34
+ */
35
+ export declare function parseWalletAuthURI(authURI: string): WalletAuthURI;
36
+ /**
37
+ * Creates a nostr+walletauth URI from a WalletAuthURI object
38
+ */
39
+ export declare function createWalletAuthURI(parts: WalletAuthURI): string;
40
+ /**
41
+ * Validates a WalletAuthURI object
42
+ * @returns true if valid, throws Error if invalid
43
+ */
44
+ export declare function validateWalletAuthURI(parts: WalletAuthURI): boolean;
@@ -0,0 +1,113 @@
1
+ import { mergeRelaySets } from "applesauce-core/helpers";
2
+ /**
3
+ * Parses a nostr+walletauth URI
4
+ * @throws {Error} if the authorization URI is invalid
5
+ */
6
+ export function parseWalletAuthURI(authURI) {
7
+ const { host, pathname, searchParams, protocol } = new URL(authURI);
8
+ // Check if it's a valid wallet auth protocol
9
+ if (!protocol.startsWith("nostr+walletauth")) {
10
+ throw new Error("invalid wallet auth uri protocol");
11
+ }
12
+ // Extract wallet name if present (nostr+walletauth+walletname://)
13
+ const walletName = protocol.includes("+") && protocol.split("+").length > 2 ? protocol.split("+")[2]?.replace(/:$/, "") : undefined;
14
+ // The client pubkey is in the pathname or host
15
+ const client = pathname || host;
16
+ if (!client)
17
+ throw new Error("missing client public key in authorization URI");
18
+ // Relay is required
19
+ const relays = mergeRelaySets(searchParams.getAll("relay"));
20
+ if (relays.length === 0)
21
+ throw new Error("missing required relay parameter in authorization URI");
22
+ // Parse optional parameters
23
+ const name = searchParams.get("name") ?? undefined;
24
+ const icon = searchParams.get("icon") ?? undefined;
25
+ const returnTo = searchParams.get("return_to") ?? undefined;
26
+ const expiresAtParam = searchParams.get("expires_at");
27
+ const expiresAt = expiresAtParam ? parseInt(expiresAtParam, 10) : undefined;
28
+ const maxAmountParam = searchParams.get("max_amount");
29
+ const maxAmount = maxAmountParam ? parseInt(maxAmountParam, 10) : undefined;
30
+ const budgetRenewal = searchParams.get("budget_renewal");
31
+ const methodsParam = searchParams.get("request_methods");
32
+ const methods = methodsParam ? methodsParam.split(" ") : undefined;
33
+ const notificationsParam = searchParams.get("notification_types");
34
+ const notifications = notificationsParam ? notificationsParam.split(" ") : undefined;
35
+ const isolatedParam = searchParams.get("isolated");
36
+ const isolated = isolatedParam ? isolatedParam === "true" : undefined;
37
+ const metadataParam = searchParams.get("metadata");
38
+ let metadata;
39
+ if (metadataParam) {
40
+ try {
41
+ metadata = JSON.parse(decodeURIComponent(metadataParam));
42
+ }
43
+ catch (error) {
44
+ throw new Error("invalid metadata parameter in authorization URI");
45
+ }
46
+ }
47
+ return {
48
+ client,
49
+ relays,
50
+ name,
51
+ icon,
52
+ returnTo,
53
+ expiresAt,
54
+ maxAmount,
55
+ budgetRenewal: budgetRenewal || undefined,
56
+ methods,
57
+ notifications,
58
+ isolated,
59
+ metadata,
60
+ walletName,
61
+ };
62
+ }
63
+ /**
64
+ * Creates a nostr+walletauth URI from a WalletAuthURI object
65
+ */
66
+ export function createWalletAuthURI(parts) {
67
+ validateWalletAuthURI(parts);
68
+ // Determine the protocol based on whether wallet name is specified
69
+ const protocol = parts.walletName ? `nostr+walletauth+${parts.walletName}` : "nostr+walletauth";
70
+ const url = new URL(`${protocol}://${parts.client}`);
71
+ // Add required relay parameter
72
+ for (const relay of parts.relays)
73
+ url.searchParams.append("relay", relay);
74
+ // Add optional parameters
75
+ if (parts.name)
76
+ url.searchParams.append("name", parts.name);
77
+ if (parts.icon)
78
+ url.searchParams.append("icon", parts.icon);
79
+ if (parts.returnTo)
80
+ url.searchParams.append("return_to", parts.returnTo);
81
+ if (parts.expiresAt)
82
+ url.searchParams.append("expires_at", parts.expiresAt.toString());
83
+ if (parts.maxAmount)
84
+ url.searchParams.append("max_amount", parts.maxAmount.toString());
85
+ if (parts.budgetRenewal && parts.budgetRenewal !== "never")
86
+ url.searchParams.append("budget_renewal", parts.budgetRenewal);
87
+ if (parts.methods && parts.methods.length > 0)
88
+ url.searchParams.append("request_methods", parts.methods.join(" "));
89
+ if (parts.notifications && parts.notifications.length > 0)
90
+ url.searchParams.append("notification_types", parts.notifications.join(" "));
91
+ if (parts.isolated !== undefined)
92
+ url.searchParams.append("isolated", parts.isolated.toString());
93
+ if (parts.metadata)
94
+ url.searchParams.append("metadata", encodeURIComponent(JSON.stringify(parts.metadata)));
95
+ return url.toString();
96
+ }
97
+ /**
98
+ * Validates a WalletAuthURI object
99
+ * @returns true if valid, throws Error if invalid
100
+ */
101
+ export function validateWalletAuthURI(parts) {
102
+ if (!parts.client || parts.client.length === 0)
103
+ throw new Error("client public key is required");
104
+ if (!parts.relays || parts.relays.length === 0)
105
+ throw new Error("at least one relay is required");
106
+ if (parts.expiresAt && parts.expiresAt <= Math.floor(Date.now() / 1000))
107
+ throw new Error("expires_at must be in the future");
108
+ if (parts.maxAmount && parts.maxAmount <= 0)
109
+ throw new Error("max_amount must be positive");
110
+ if (parts.budgetRenewal && !["never", "daily", "weekly", "monthly", "yearly"].includes(parts.budgetRenewal))
111
+ throw new Error("invalid budget_renewal value");
112
+ return true;
113
+ }
@@ -1,7 +1,12 @@
1
1
  export interface WalletConnectURI {
2
+ /** The pubkey of the wallet service */
2
3
  service: string;
4
+ /** The relays to use for the connection */
3
5
  relays: string[];
6
+ /** The secret key that the client will use to encrypt messages */
4
7
  secret: string;
8
+ /** An optional lub16 lightning address that is associated with the wallet */
9
+ lud16?: string;
5
10
  }
6
11
  /**
7
12
  * Parses a nostr+walletconnect URI
@@ -1,3 +1,4 @@
1
+ import { mergeRelaySets } from "applesauce-core/helpers";
1
2
  /**
2
3
  * Parses a nostr+walletconnect URI
3
4
  * @throws {Error} if the connection string is invalid
@@ -7,11 +8,12 @@ export function parseWalletConnectURI(connectionString) {
7
8
  if (protocol !== "nostr+walletconnect:")
8
9
  throw new Error("invalid wallet connect uri protocol");
9
10
  const service = pathname || host;
10
- const relays = searchParams.getAll("relay");
11
+ const relays = mergeRelaySets(searchParams.getAll("relay"));
11
12
  const secret = searchParams.get("secret");
13
+ const lud16 = searchParams.get("lud16") ?? undefined;
12
14
  if (!service || relays.length === 0 || !secret)
13
15
  throw new Error("invalid connection string");
14
- return { service, relays, secret };
16
+ return { service, relays, secret, lud16 };
15
17
  }
16
18
  /** Creates a nostr+walletconnect URI from a WalletConnectURI object */
17
19
  export function createWalletConnectURI(parts) {
@@ -19,5 +21,7 @@ export function createWalletConnectURI(parts) {
19
21
  for (const relay of parts.relays)
20
22
  url.searchParams.append("relay", relay);
21
23
  url.searchParams.append("secret", parts.secret);
24
+ if (parts.lud16)
25
+ url.searchParams.append("lud16", parts.lud16);
22
26
  return url.toString();
23
27
  }
@@ -5,3 +5,4 @@ export * from "./response.js";
5
5
  export * from "./support.js";
6
6
  export * from "./notification.js";
7
7
  export * from "./encryption.js";
8
+ export * from "./auth-uri.js";
@@ -5,3 +5,4 @@ export * from "./response.js";
5
5
  export * from "./support.js";
6
6
  export * from "./notification.js";
7
7
  export * from "./encryption.js";
8
+ export * from "./auth-uri.js";
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export * as Blueprints from "./blueprints/index.js";
2
2
  export * as Helpers from "./helpers/index.js";
3
3
  export * from "./wallet-connect.js";
4
4
  export * from "./wallet-service.js";
5
- export * from "./types.js";
5
+ export * from "./interop.js";
package/dist/index.js CHANGED
@@ -2,4 +2,4 @@ export * as Blueprints from "./blueprints/index.js";
2
2
  export * as Helpers from "./helpers/index.js";
3
3
  export * from "./wallet-connect.js";
4
4
  export * from "./wallet-service.js";
5
- export * from "./types.js";
5
+ export * from "./interop.js";
@@ -0,0 +1,35 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { ObservableInput } from "rxjs";
3
+ /** A method used to subscribe to events on a set of relays */
4
+ export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => ObservableInput<NostrEvent | string>;
5
+ /** A method used for publishing an event, can return a Promise that completes when published or an Observable that completes when published*/
6
+ export type NostrPublishMethod = (relays: string[], event: NostrEvent) => Promise<any> | ObservableInput<any>;
7
+ /** A simple pool type that combines the subscription and publish methods */
8
+ export type NostrPool = {
9
+ subscription: NostrSubscriptionMethod;
10
+ publish: NostrPublishMethod;
11
+ };
12
+ /** Options for setting the subscription and publish methods */
13
+ export type NostrConnectionMethodsOptions = {
14
+ /** An optional method for subscribing to relays */
15
+ subscriptionMethod?: NostrSubscriptionMethod;
16
+ /** An optional method for publishing events */
17
+ publishMethod?: NostrPublishMethod;
18
+ /** An optional pool for connection methods */
19
+ pool?: NostrPool;
20
+ };
21
+ /** A class that implements has global fallback methods for subscription and publish methods */
22
+ export interface NostrConnectionClassMethods {
23
+ new (...args: any[]): any;
24
+ /** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
25
+ subscriptionMethod: NostrSubscriptionMethod | undefined;
26
+ /** A fallback method to use for publishMethod if none is passed in when creating the client */
27
+ publishMethod: NostrPublishMethod | undefined;
28
+ /** A fallback pool to use if none is pass in when creating the signer */
29
+ pool: NostrPool | undefined;
30
+ }
31
+ /** Get the subscription and publish methods for a NostrConnect class */
32
+ export declare function getConnectionMethods(options: NostrConnectionMethodsOptions, cls?: NostrConnectionClassMethods): {
33
+ subscriptionMethod: NostrSubscriptionMethod;
34
+ publishMethod: NostrPublishMethod;
35
+ };
@@ -0,0 +1,19 @@
1
+ /** Get the subscription and publish methods for a NostrConnect class */
2
+ export function getConnectionMethods(options, cls) {
3
+ const subscriptionMethod = options.subscriptionMethod ||
4
+ options.pool?.subscription.bind(options.pool) ||
5
+ cls?.subscriptionMethod ||
6
+ cls?.pool?.subscription.bind(cls.pool);
7
+ if (!subscriptionMethod)
8
+ throw new Error("Missing subscriptionMethod, either pass a method or set subscriptionMethod globally on the class");
9
+ const publishMethod = options.publishMethod ||
10
+ options.pool?.publish.bind(options.pool) ||
11
+ cls?.publishMethod ||
12
+ cls?.pool?.publish.bind(cls.pool);
13
+ if (!publishMethod)
14
+ throw new Error("Missing publishMethod, either pass a method or set publishMethod globally on the class");
15
+ return {
16
+ subscriptionMethod,
17
+ publishMethod,
18
+ };
19
+ }
package/dist/types.d.ts CHANGED
@@ -4,3 +4,8 @@ import { Observable } from "rxjs";
4
4
  export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Observable<NostrEvent | string>;
5
5
  /** A method used for publishing an event, can return a Promise that completes when published or an Observable that completes when published*/
6
6
  export type NostrPublishMethod = (relays: string[], event: NostrEvent) => Promise<any> | Observable<any>;
7
+ /** A simple pool type that combines the subscription and publish methods */
8
+ export type NostrPool = {
9
+ subscription: NostrSubscriptionMethod;
10
+ publish: NostrPublishMethod;
11
+ };
@@ -1,15 +1,17 @@
1
+ import { EncryptionMethod } from "applesauce-core/helpers";
1
2
  import { EventSigner } from "applesauce-factory";
2
3
  import { NostrEvent } from "nostr-tools";
3
- import { Observable, Subscription } from "rxjs";
4
- import { EncryptionMethod } from "applesauce-core/helpers";
5
- import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceParams, MakeInvoiceResult, NotificationType, PayInvoiceResult, PayKeysendResult, WalletConnectEncryptionMethod, WalletConnectURI, WalletMethod, WalletNotification, WalletRequest, WalletResponse, WalletSupport } from "./helpers/index.js";
6
- import { NostrPublishMethod, NostrSubscriptionMethod } from "./types.js";
4
+ import { BehaviorSubject, Observable, Subscription } from "rxjs";
5
+ import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceParams, MakeInvoiceResult, NotificationType, PayInvoiceResult, PayKeysendResult, WalletAuthURI, WalletConnectEncryptionMethod, WalletConnectURI, WalletMethod, WalletNotification, WalletRequest, WalletResponse, WalletSupport } from "./helpers/index.js";
6
+ import { NostrConnectionMethodsOptions, NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./interop.js";
7
7
  export type SerializedWalletConnect = WalletConnectURI;
8
- export type WalletConnectOptions = {
9
- /** A method for subscribing to relays */
10
- subscriptionMethod?: NostrSubscriptionMethod;
11
- /** A method for publishing events */
12
- publishMethod?: NostrPublishMethod;
8
+ export type WalletConnectOptions = NostrConnectionMethodsOptions & {
9
+ /** The secret to use for the connection */
10
+ secret: Uint8Array;
11
+ /** The relays to use for the connection */
12
+ relays: string[];
13
+ /** The service pubkey to use for the connection (optional) */
14
+ service?: string;
13
15
  /** Default timeout for RPC requests in milliseconds */
14
16
  timeout?: number;
15
17
  };
@@ -18,6 +20,8 @@ export declare class WalletConnect {
18
20
  static subscriptionMethod: NostrSubscriptionMethod | undefined;
19
21
  /** A fallback method to use for publishMethod if none is passed in when creating the client */
20
22
  static publishMethod: NostrPublishMethod | undefined;
23
+ /** A fallback pool to use if none is pass in when creating the signer */
24
+ static pool: NostrPool | undefined;
21
25
  /** A method that is called when an event needs to be published */
22
26
  protected publishMethod: NostrPublishMethod;
23
27
  /** The active nostr subscription method */
@@ -27,8 +31,9 @@ export declare class WalletConnect {
27
31
  protected readonly signer: EventSigner;
28
32
  /** The relays to use for the connection */
29
33
  readonly relays: string[];
30
- /** The wallet service public key */
31
- readonly service: string;
34
+ /** The wallet service public key ( unset if waiting for service ) */
35
+ service$: BehaviorSubject<string | undefined>;
36
+ get service(): string | undefined;
32
37
  /** Default timeout for requests */
33
38
  defaultTimeout: number;
34
39
  /** Observable for wallet info updates */
@@ -39,7 +44,9 @@ export declare class WalletConnect {
39
44
  protected events$: Observable<NostrEvent>;
40
45
  /** Shared observable for all wallet notifications */
41
46
  notifications$: Observable<WalletNotification>;
42
- constructor(secret: Uint8Array, service: string, relays: string[], options?: WalletConnectOptions);
47
+ /** An internal observable for listening for the wallet service to connect */
48
+ protected waitForService$: Observable<string>;
49
+ constructor(options: WalletConnectOptions);
43
50
  /** Process response events and return WalletResponse or throw error */
44
51
  protected handleResponseEvent(event: NostrEvent, encryption?: EncryptionMethod): Promise<WalletResponse>;
45
52
  /** Handle notification events */
@@ -53,6 +60,10 @@ export declare class WalletConnect {
53
60
  * @returns a method to unsubscribe the listener
54
61
  */
55
62
  notification<T extends WalletNotification>(type: T["notification_type"], listener: (notification: T["notification"]) => any): Subscription;
63
+ /** Gets the nostr+walletauth URI for the connection */
64
+ getAuthURI(parts?: Omit<WalletAuthURI, "client" | "relays">): string;
65
+ /** Wait for the wallet service to connect */
66
+ waitForService(abortSignal?: AbortSignal): Promise<string>;
56
67
  /** Get the wallet support info */
57
68
  getSupport(): Promise<WalletSupport | null>;
58
69
  /** Check if the wallet supports a method */
@@ -105,7 +116,7 @@ export declare class WalletConnect {
105
116
  /** Serialize the WalletConnect instance */
106
117
  toJSON(): SerializedWalletConnect;
107
118
  /** Create a new WalletConnect instance from a serialized object */
108
- static fromJSON(json: SerializedWalletConnect, options?: WalletConnectOptions): WalletConnect;
119
+ static fromJSON(json: SerializedWalletConnect, options?: Omit<WalletConnectOptions, "secret" | "relays" | "service">): WalletConnect;
109
120
  /** Create a new WalletConnect instance from a connection string */
110
- static fromConnectionString(connectionString: string, options?: WalletConnectOptions): WalletConnect;
121
+ static fromConnectURI(connectionString: string, options?: Omit<WalletConnectOptions, "secret" | "relays" | "service">): WalletConnect;
111
122
  }
@@ -2,15 +2,18 @@ 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 { defer, filter, firstValueFrom, from, ignoreElements, lastValueFrom, map, merge, mergeMap, ReplaySubject, share, switchMap, timer, toArray, } from "rxjs";
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";
6
6
  import { WalletRequestBlueprint } from "./blueprints/index.js";
7
7
  import { createWalletError } from "./helpers/error.js";
8
- import { getPreferredEncryption, getWalletNotification, getWalletRequestEncryption, getWalletResponse, getWalletResponseRequestId, getWalletSupport, isWalletNotificationLocked, isWalletResponseLocked, parseWalletConnectURI, supportsMethod, supportsNotifications, supportsNotificationType, unlockWalletNotification, unlockWalletResponse, WALLET_INFO_KIND, WALLET_LEGACY_NOTIFICATION_KIND, WALLET_NOTIFICATION_KIND, WALLET_RESPONSE_KIND, } from "./helpers/index.js";
8
+ import { createWalletAuthURI, getPreferredEncryption, getWalletNotification, getWalletRequestEncryption, getWalletResponse, getWalletResponseRequestId, getWalletSupport, isWalletNotificationLocked, isWalletResponseLocked, parseWalletConnectURI, supportsMethod, supportsNotifications, supportsNotificationType, unlockWalletNotification, unlockWalletResponse, WALLET_INFO_KIND, WALLET_LEGACY_NOTIFICATION_KIND, WALLET_NOTIFICATION_KIND, WALLET_RESPONSE_KIND, } from "./helpers/index.js";
9
+ import { getConnectionMethods, } from "./interop.js";
9
10
  export class WalletConnect {
10
11
  /** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
11
12
  static subscriptionMethod = undefined;
12
13
  /** A fallback method to use for publishMethod if none is passed in when creating the client */
13
14
  static publishMethod = undefined;
15
+ /** A fallback pool to use if none is pass in when creating the signer */
16
+ static pool = undefined;
14
17
  /** A method that is called when an event needs to be published */
15
18
  publishMethod;
16
19
  /** The active nostr subscription method */
@@ -20,8 +23,11 @@ export class WalletConnect {
20
23
  signer;
21
24
  /** The relays to use for the connection */
22
25
  relays;
23
- /** The wallet service public key */
24
- service;
26
+ /** The wallet service public key ( unset if waiting for service ) */
27
+ service$ = new BehaviorSubject(undefined);
28
+ get service() {
29
+ return this.service$.value;
30
+ }
25
31
  /** Default timeout for requests */
26
32
  defaultTimeout;
27
33
  /** Observable for wallet info updates */
@@ -32,10 +38,12 @@ export class WalletConnect {
32
38
  events$;
33
39
  /** Shared observable for all wallet notifications */
34
40
  notifications$;
35
- constructor(secret, service, relays, options = {}) {
36
- this.service = service;
37
- this.secret = secret;
38
- this.relays = relays;
41
+ /** An internal observable for listening for the wallet service to connect */
42
+ waitForService$;
43
+ constructor(options) {
44
+ this.secret = options.secret;
45
+ this.relays = options.relays;
46
+ this.service$.next(options.service);
39
47
  this.defaultTimeout = options.timeout || 30000; // 30 second default timeout
40
48
  // Create a signer for the factory
41
49
  this.signer = {
@@ -50,29 +58,42 @@ export class WalletConnect {
50
58
  decrypt: async (pubkey, ciphertext) => nip44.decrypt(ciphertext, nip44.getConversationKey(this.secret, pubkey)),
51
59
  },
52
60
  };
53
- const subscriptionMethod = options.subscriptionMethod || WalletConnect.subscriptionMethod;
54
- if (!subscriptionMethod)
55
- throw new Error("Missing subscriptionMethod, either pass a method or set WalletConnect.subscriptionMethod");
56
- const publishMethod = options.publishMethod || WalletConnect.publishMethod;
57
- if (!publishMethod)
58
- throw new Error("Missing publishMethod, either pass a method or set WalletConnect.publishMethod");
59
- this.subscriptionMethod = subscriptionMethod;
60
- this.publishMethod = publishMethod;
61
- // Create shared response observable with ref counting and timer
62
- this.events$ = defer(() => this.signer.getPublicKey()).pipe(switchMap((client) => this.subscriptionMethod(this.relays, [
63
- // Subscribe to response events
64
- {
65
- kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
66
- "#p": [client],
67
- authors: [this.service],
68
- },
69
- // Subscribe to wallet info events
70
- { kinds: [WALLET_INFO_KIND], authors: [this.service] },
71
- ])),
72
- // Ingore strings (support for applesauce-relay)
73
- filter((event) => typeof event !== "string"),
74
- // Only include events from the wallet service
75
- filter((event) => event.pubkey === this.service),
61
+ // Get the subscription and publish methods
62
+ const { subscriptionMethod, publishMethod } = getConnectionMethods(options, WalletConnect);
63
+ // Use arrow functions so "this" isn't bound to the signer
64
+ this.subscriptionMethod = (relays, filters) => subscriptionMethod(relays, filters);
65
+ this.publishMethod = (relays, event) => publishMethod(relays, event);
66
+ // Create shared observable for all wallet events
67
+ this.events$ = this.service$.pipe(switchMap((service) => {
68
+ const client = getPublicKey(this.secret);
69
+ // If the service is not known yet, subscribe to a wallet info event tagging the client
70
+ if (!service)
71
+ return from(this.subscriptionMethod(this.relays, [{ kinds: [WALLET_INFO_KIND], "#p": [client] }])).pipe(
72
+ // Keep the connection open indefinitely
73
+ repeat(),
74
+ // Retry on connection failure
75
+ retry(),
76
+ // Ignore strings (support for applesauce-relay)
77
+ filter((event) => typeof event !== "string"));
78
+ return from(this.subscriptionMethod(this.relays, [
79
+ // Subscribe to response events
80
+ {
81
+ kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
82
+ "#p": [client],
83
+ authors: [service],
84
+ },
85
+ // Subscribe to wallet info events
86
+ { kinds: [WALLET_INFO_KIND], authors: [service] },
87
+ ])).pipe(
88
+ // Keep the connection open indefinitely
89
+ repeat(),
90
+ // Retry on connection failure
91
+ retry(),
92
+ // Ignore strings (support for applesauce-relay)
93
+ filter((event) => typeof event !== "string"),
94
+ // Only include events from the wallet service
95
+ filter((event) => event.pubkey === service));
96
+ }),
76
97
  // Only create a single subscription to the relays
77
98
  share({
78
99
  resetOnRefCountZero: () => timer(60000), // Keep subscription open for 1 minute after last unsubscribe
@@ -83,6 +104,20 @@ export class WalletConnect {
83
104
  }));
84
105
  this.encryption$ = this.support$.pipe(map((info) => (info ? getPreferredEncryption(info) : "nip04")));
85
106
  this.notifications$ = this.events$.pipe(filter((event) => event.kind === WALLET_NOTIFICATION_KIND), 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),
119
+ // Only create a single subscription to avoid multiple side effects
120
+ share());
86
121
  }
87
122
  /** Process response events and return WalletResponse or throw error */
88
123
  async handleResponseEvent(event, encryption) {
@@ -115,6 +150,8 @@ export class WalletConnect {
115
150
  }
116
151
  /** Core RPC method that makes a request and returns the response */
117
152
  request(request, options = {}) {
153
+ if (!this.service)
154
+ throw new Error("WalletConnect is not connected to a service");
118
155
  // Create the request evnet
119
156
  return defer(async () => {
120
157
  // Get the preferred encryption method for the wallet
@@ -146,6 +183,18 @@ export class WalletConnect {
146
183
  listener(notification.notification);
147
184
  });
148
185
  }
186
+ /** Gets the nostr+walletauth URI for the connection */
187
+ getAuthURI(parts) {
188
+ return createWalletAuthURI({ ...parts, client: getPublicKey(this.secret), relays: this.relays });
189
+ }
190
+ /** Wait for the wallet service to connect */
191
+ async waitForService(abortSignal) {
192
+ if (this.service)
193
+ return this.service;
194
+ return await firstValueFrom(this.waitForService$.pipe(
195
+ // Listen for abort signal
196
+ abortSignal ? takeUntil(fromEvent(abortSignal, "abort")) : identity));
197
+ }
149
198
  // Convenience methods that return promises for easy API usage
150
199
  /** Get the wallet support info */
151
200
  getSupport() {
@@ -253,6 +302,8 @@ export class WalletConnect {
253
302
  }
254
303
  /** Serialize the WalletConnect instance */
255
304
  toJSON() {
305
+ if (!this.service)
306
+ throw new Error("WalletConnect is not connected to a service");
256
307
  return {
257
308
  secret: bytesToHex(this.secret),
258
309
  service: this.service,
@@ -261,11 +312,21 @@ export class WalletConnect {
261
312
  }
262
313
  /** Create a new WalletConnect instance from a serialized object */
263
314
  static fromJSON(json, options) {
264
- return new WalletConnect(hexToBytes(json.secret), json.service, json.relays, options);
315
+ return new WalletConnect({
316
+ ...options,
317
+ secret: hexToBytes(json.secret),
318
+ service: json.service,
319
+ relays: json.relays,
320
+ });
265
321
  }
266
322
  /** Create a new WalletConnect instance from a connection string */
267
- static fromConnectionString(connectionString, options) {
323
+ static fromConnectURI(connectionString, options) {
268
324
  const { secret, service, relays } = parseWalletConnectURI(connectionString);
269
- return new WalletConnect(hexToBytes(secret), service, relays, options);
325
+ return new WalletConnect({
326
+ ...options,
327
+ secret: hexToBytes(secret),
328
+ service,
329
+ relays,
330
+ });
270
331
  }
271
332
  }