applesauce-wallet-connect 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,16 @@
1
- import { HiddenContentSigner } from "applesauce-core/helpers";
1
+ import { HiddenContentSigner, KnownEvent, UnlockedHiddenContent } from "applesauce-core/helpers";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  import { Transaction } from "./response.js";
4
4
  export declare const WALLET_NOTIFICATION_KIND = 23197;
5
5
  export declare const WALLET_LEGACY_NOTIFICATION_KIND = 23196;
6
+ /** A type for validated wallet notification events */
7
+ export type WalletNotificationEvent = KnownEvent<typeof WALLET_NOTIFICATION_KIND> | KnownEvent<typeof WALLET_LEGACY_NOTIFICATION_KIND>;
6
8
  /** A symbol used to cache the wallet notification on the event */
7
9
  export declare const WalletNotificationSymbol: unique symbol;
10
+ /** A type for unlocked notifications events */
11
+ export type UnlockedWalletNotification = UnlockedHiddenContent & {
12
+ [WalletNotificationSymbol]: WalletNotification;
13
+ };
8
14
  /** Supported notification types */
9
15
  export type NotificationType = "payment_received" | "payment_sent";
10
16
  /** Base notification structure */
@@ -21,8 +27,10 @@ export type PaymentSentNotification = BaseNotification<"payment_sent", Transacti
21
27
  /** Union type for all NIP-47 notification types */
22
28
  export type WalletNotification = PaymentReceivedNotification | PaymentSentNotification;
23
29
  /** Checks if a kind 23196 or 23197 event is locked */
24
- export declare function isWalletNotificationLocked(notification: NostrEvent): boolean;
30
+ export declare function isWalletNotificationUnlocked(notification: any): notification is UnlockedWalletNotification;
25
31
  /** Unlocks a kind 23196 or 23197 event */
26
- export declare function unlockWalletNotification(notification: NostrEvent, signer: HiddenContentSigner): Promise<WalletNotification | undefined | null>;
32
+ export declare function unlockWalletNotification(notification: NostrEvent, signer: HiddenContentSigner): Promise<WalletNotification | undefined>;
27
33
  /** Gets the wallet notification from a kind 23196 or 23197 event */
28
- export declare function getWalletNotification(notification: NostrEvent): WalletNotification | undefined | null;
34
+ export declare function getWalletNotification(notification: NostrEvent): WalletNotification | undefined;
35
+ /** Checks if an event is a valid wallet notification event */
36
+ export declare function isValidWalletNotification(notification: NostrEvent): notification is WalletNotificationEvent;
@@ -1,4 +1,4 @@
1
- import { getHiddenContent, getOrComputeCachedValue, isHiddenContentLocked, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
1
+ import { isHiddenContentUnlocked, notifyEventUpdate, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
2
2
  export const WALLET_NOTIFICATION_KIND = 23197;
3
3
  export const WALLET_LEGACY_NOTIFICATION_KIND = 23196;
4
4
  /** A symbol used to cache the wallet notification on the event */
@@ -7,22 +7,28 @@ export const WalletNotificationSymbol = Symbol("wallet-notification");
7
7
  setHiddenContentEncryptionMethod(WALLET_NOTIFICATION_KIND, "nip44");
8
8
  setHiddenContentEncryptionMethod(WALLET_LEGACY_NOTIFICATION_KIND, "nip04");
9
9
  /** Checks if a kind 23196 or 23197 event is locked */
10
- export function isWalletNotificationLocked(notification) {
11
- return isHiddenContentLocked(notification);
10
+ export function isWalletNotificationUnlocked(notification) {
11
+ return isHiddenContentUnlocked(notification) && Reflect.has(notification, WalletNotificationSymbol) === true;
12
12
  }
13
13
  /** Unlocks a kind 23196 or 23197 event */
14
14
  export async function unlockWalletNotification(notification, signer) {
15
- await unlockHiddenContent(notification, signer);
16
- return getWalletNotification(notification);
15
+ if (isWalletNotificationUnlocked(notification))
16
+ return notification[WalletNotificationSymbol];
17
+ const content = await unlockHiddenContent(notification, signer);
18
+ const parsed = JSON.parse(content);
19
+ // Save the parsed content
20
+ Reflect.set(notification, WalletNotificationSymbol, parsed);
21
+ notifyEventUpdate(notification);
22
+ return parsed;
17
23
  }
18
24
  /** Gets the wallet notification from a kind 23196 or 23197 event */
19
25
  export function getWalletNotification(notification) {
20
- if (isWalletNotificationLocked(notification))
26
+ if (isWalletNotificationUnlocked(notification))
27
+ return notification[WalletNotificationSymbol];
28
+ else
21
29
  return undefined;
22
- return getOrComputeCachedValue(notification, WalletNotificationSymbol, () => {
23
- const content = getHiddenContent(notification);
24
- if (!content)
25
- return null;
26
- return JSON.parse(content);
27
- });
30
+ }
31
+ /** Checks if an event is a valid wallet notification event */
32
+ export function isValidWalletNotification(notification) {
33
+ return notification.kind === WALLET_NOTIFICATION_KIND || notification.kind === WALLET_LEGACY_NOTIFICATION_KIND;
28
34
  }
@@ -1,10 +1,15 @@
1
- import { HiddenContentSigner } from "applesauce-core/helpers";
1
+ import { HiddenContentSigner, KnownEvent, UnlockedHiddenContent } from "applesauce-core/helpers";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  import { WalletConnectEncryptionMethod } from "./encryption.js";
4
4
  import { WalletMethod } from "./support.js";
5
5
  export declare const WALLET_REQUEST_KIND = 23194;
6
+ export type WalletRequestEvent = KnownEvent<typeof WALLET_REQUEST_KIND>;
6
7
  /** A symbol used to cache the wallet request on the event */
7
8
  export declare const WalletRequestSymbol: unique symbol;
9
+ /** Type for events with unlocked hidden content */
10
+ export type UnlockedWalletRequest = UnlockedHiddenContent & {
11
+ [WalletRequestSymbol]: WalletRequest;
12
+ };
8
13
  /** TLV record for keysend payments */
9
14
  export interface TLVRecord {
10
15
  /** TLV type */
@@ -116,12 +121,13 @@ export type GetInfoRequest = BaseWalletRequest<"get_info", GetInfoParams>;
116
121
  /** Union type for all NIP-47 request types */
117
122
  export type WalletRequest = PayInvoiceRequest | MultiPayInvoiceRequest | PayKeysendRequest | MultiPayKeysendRequest | MakeInvoiceRequest | LookupInvoiceRequest | ListTransactionsRequest | GetBalanceRequest | GetInfoRequest;
118
123
  /** Checks if a kind 23194 event is locked */
119
- export declare function isWalletRequestLocked(request: NostrEvent): boolean;
124
+ export declare function isWalletRequestUnlocked(request: any): request is UnlockedWalletRequest;
120
125
  /** Unlocks a kind 23194 event */
121
- export declare function unlockWalletRequest(request: NostrEvent, signer: HiddenContentSigner): Promise<WalletRequest | undefined | null>;
126
+ export declare function unlockWalletRequest(request: NostrEvent, signer: HiddenContentSigner): Promise<WalletRequest | undefined>;
122
127
  /** Gets the wallet request from a kind 23194 event */
123
- export declare function getWalletRequest(request: NostrEvent): WalletRequest | undefined | null;
128
+ export declare function getWalletRequest(request: NostrEvent): WalletRequest | undefined;
124
129
  /** Returns the wallet service pubkey from a request */
130
+ export declare function getWalletRequestServicePubkey(request: WalletRequestEvent): string;
125
131
  export declare function getWalletRequestServicePubkey(request: NostrEvent): string | undefined;
126
132
  /** Returns the expiration timestamp from a request */
127
133
  export declare function getWalletRequestExpiration(request: NostrEvent): number | undefined;
@@ -129,3 +135,5 @@ export declare function getWalletRequestExpiration(request: NostrEvent): number
129
135
  export declare function isWalletRequestExpired(request: NostrEvent): boolean;
130
136
  /** Gets the encryption method used for a request */
131
137
  export declare function getWalletRequestEncryption(request: NostrEvent): WalletConnectEncryptionMethod;
138
+ /** Checks if an event is a valid wallet request event */
139
+ export declare function isValidWalletRequest(request: NostrEvent): request is WalletRequestEvent;
@@ -1,30 +1,31 @@
1
- import { getHiddenContent, getOrComputeCachedValue, getTagValue, isHiddenContentLocked, isNIP04Encrypted, setHiddenContentEncryptionMethod, unixNow, unlockHiddenContent, } from "applesauce-core/helpers";
1
+ import { getTagValue, isHiddenContentUnlocked, isNIP04Encrypted, notifyEventUpdate, setHiddenContentEncryptionMethod, unixNow, unlockHiddenContent, } from "applesauce-core/helpers";
2
2
  export const WALLET_REQUEST_KIND = 23194;
3
3
  // Set the encryption method to use for request kind
4
4
  setHiddenContentEncryptionMethod(WALLET_REQUEST_KIND, "nip44");
5
5
  /** A symbol used to cache the wallet request on the event */
6
6
  export const WalletRequestSymbol = Symbol("wallet-request");
7
7
  /** Checks if a kind 23194 event is locked */
8
- export function isWalletRequestLocked(request) {
9
- return isHiddenContentLocked(request);
8
+ export function isWalletRequestUnlocked(request) {
9
+ return isHiddenContentUnlocked(request) && Reflect.has(request, WalletRequestSymbol) === true;
10
10
  }
11
11
  /** Unlocks a kind 23194 event */
12
12
  export async function unlockWalletRequest(request, signer) {
13
- await unlockHiddenContent(request, signer);
14
- return getWalletRequest(request);
13
+ if (isWalletRequestUnlocked(request))
14
+ return request[WalletRequestSymbol];
15
+ const content = await unlockHiddenContent(request, signer);
16
+ const parsed = JSON.parse(content);
17
+ // Save the parsed content
18
+ Reflect.set(request, WalletRequestSymbol, parsed);
19
+ notifyEventUpdate(request);
20
+ return parsed;
15
21
  }
16
22
  /** Gets the wallet request from a kind 23194 event */
17
23
  export function getWalletRequest(request) {
18
- if (isWalletRequestLocked(request))
24
+ if (isWalletRequestUnlocked(request))
25
+ return request[WalletRequestSymbol];
26
+ else
19
27
  return undefined;
20
- return getOrComputeCachedValue(request, WalletRequestSymbol, () => {
21
- const content = getHiddenContent(request);
22
- if (!content)
23
- return null;
24
- return JSON.parse(content);
25
- });
26
- }
27
- /** Returns the wallet service pubkey from a request */
28
+ }
28
29
  export function getWalletRequestServicePubkey(request) {
29
30
  return getTagValue(request, "p");
30
31
  }
@@ -49,3 +50,7 @@ export function getWalletRequestEncryption(request) {
49
50
  ? "nip04"
50
51
  : "nip44_v2";
51
52
  }
53
+ /** Checks if an event is a valid wallet request event */
54
+ export function isValidWalletRequest(request) {
55
+ return request.kind === WALLET_REQUEST_KIND && getWalletRequestServicePubkey(request) !== undefined;
56
+ }
@@ -1,11 +1,17 @@
1
- import { EncryptionMethod, HiddenContentSigner } from "applesauce-core/helpers";
1
+ import { EncryptionMethod, HiddenContentSigner, KnownEvent, UnlockedHiddenContent } from "applesauce-core/helpers";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  import { WalletErrorCode } from "./error.js";
4
- import { WalletMethod } from "./support.js";
5
4
  import { NotificationType } from "./notification.js";
5
+ import { WalletMethod } from "./support.js";
6
6
  export declare const WALLET_RESPONSE_KIND = 23195;
7
7
  /** A symbol used to cache the wallet response on the event */
8
8
  export declare const WalletResponseSymbol: unique symbol;
9
+ /** Type for events with unlocked hidden content */
10
+ export type UnlockedWalletResponse = UnlockedHiddenContent & {
11
+ [WalletResponseSymbol]: WalletResponse;
12
+ };
13
+ /** Type for validated wallet response events */
14
+ export type WalletResponseEvent = KnownEvent<typeof WALLET_RESPONSE_KIND>;
9
15
  /** Error object for wallet responses */
10
16
  export interface WalletResponseError {
11
17
  type: WalletErrorCode;
@@ -127,12 +133,16 @@ export type GetInfoResponse = BaseWalletResponse<"get_info", GetInfoResult>;
127
133
  /** Union type for all NIP-47 response types */
128
134
  export type WalletResponse = PayInvoiceResponse | MultiPayInvoiceResponse | PayKeysendResponse | MultiPayKeysendResponse | MakeInvoiceResponse | LookupInvoiceResponse | ListTransactionsResponse | GetBalanceResponse | GetInfoResponse;
129
135
  /** Checks if a kind 23195 event is locked */
130
- export declare function isWalletResponseLocked(response: NostrEvent): boolean;
136
+ export declare function isWalletResponseUnlocked(response: any): response is UnlockedWalletResponse;
131
137
  /** Unlocks a kind 23195 event */
132
- export declare function unlockWalletResponse(response: NostrEvent, signer: HiddenContentSigner, override?: EncryptionMethod): Promise<WalletResponse | undefined | null>;
138
+ export declare function unlockWalletResponse(response: NostrEvent, signer: HiddenContentSigner, override?: EncryptionMethod): Promise<WalletResponse | undefined>;
133
139
  /** Gets the wallet response from a kind 23195 event */
134
- export declare function getWalletResponse(response: NostrEvent): WalletResponse | undefined | null;
140
+ export declare function getWalletResponse(response: NostrEvent): WalletResponse | undefined;
135
141
  /** Returns the client pubkey of client this response is for */
142
+ export declare function getWalletResponseClientPubkey(response: WalletResponseEvent): string;
136
143
  export declare function getWalletResponseClientPubkey(response: NostrEvent): string | undefined;
137
144
  /** Returns the request id of the request this response is for */
145
+ export declare function getWalletResponseRequestId(response: WalletResponseEvent): string;
138
146
  export declare function getWalletResponseRequestId(response: NostrEvent): string | undefined;
147
+ /** Checks if event is a valid wallet response event */
148
+ export declare function isValidWalletResponse(response: NostrEvent): response is WalletResponseEvent;
@@ -1,35 +1,41 @@
1
- import { getHiddenContent, getOrComputeCachedValue, getTagValue, isHiddenContentLocked, isNIP04Encrypted, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
1
+ import { getTagValue, isHiddenContentUnlocked, isNIP04Encrypted, notifyEventUpdate, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
2
2
  export const WALLET_RESPONSE_KIND = 23195;
3
3
  // Set the encryption method to use for response kind
4
4
  setHiddenContentEncryptionMethod(WALLET_RESPONSE_KIND, "nip04");
5
5
  /** A symbol used to cache the wallet response on the event */
6
6
  export const WalletResponseSymbol = Symbol("wallet-response");
7
7
  /** Checks if a kind 23195 event is locked */
8
- export function isWalletResponseLocked(response) {
9
- return isHiddenContentLocked(response);
8
+ export function isWalletResponseUnlocked(response) {
9
+ return isHiddenContentUnlocked(response) && Reflect.has(response, WalletResponseSymbol) === true;
10
10
  }
11
11
  /** Unlocks a kind 23195 event */
12
12
  export async function unlockWalletResponse(response, signer, override) {
13
+ if (isWalletResponseUnlocked(response))
14
+ return response[WalletResponseSymbol];
13
15
  const encryption = override ?? (!isNIP04Encrypted(response.content) ? "nip44" : "nip04");
14
- await unlockHiddenContent(response, signer, encryption);
15
- return getWalletResponse(response);
16
+ const content = await unlockHiddenContent(response, signer, encryption);
17
+ const parsed = JSON.parse(content);
18
+ // Save the parsed content
19
+ Reflect.set(response, WalletResponseSymbol, parsed);
20
+ notifyEventUpdate(response);
21
+ return parsed;
16
22
  }
17
23
  /** Gets the wallet response from a kind 23195 event */
18
24
  export function getWalletResponse(response) {
19
- if (isWalletResponseLocked(response))
25
+ if (isWalletResponseUnlocked(response))
26
+ return response[WalletResponseSymbol];
27
+ else
20
28
  return undefined;
21
- return getOrComputeCachedValue(response, WalletResponseSymbol, () => {
22
- const content = getHiddenContent(response);
23
- if (!content)
24
- return null;
25
- return JSON.parse(content);
26
- });
27
29
  }
28
- /** Returns the client pubkey of client this response is for */
29
30
  export function getWalletResponseClientPubkey(response) {
30
31
  return getTagValue(response, "p");
31
32
  }
32
- /** Returns the request id of the request this response is for */
33
33
  export function getWalletResponseRequestId(response) {
34
34
  return getTagValue(response, "e");
35
35
  }
36
+ /** Checks if event is a valid wallet response event */
37
+ export function isValidWalletResponse(response) {
38
+ return (response.kind === WALLET_RESPONSE_KIND &&
39
+ getWalletResponseRequestId(response) !== undefined &&
40
+ getWalletResponseClientPubkey(response) !== undefined);
41
+ }
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
+ }
@@ -2,10 +2,10 @@ import { EncryptionMethod } from "applesauce-core/helpers";
2
2
  import { EventSigner } from "applesauce-factory";
3
3
  import { NostrEvent } from "nostr-tools";
4
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 { NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./types.js";
5
+ import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceParams, MakeInvoiceResult, NotificationType, PayInvoiceResult, PayKeysendResult, WalletAuthURI, WalletConnectEncryptionMethod, WalletConnectURI, WalletMethod, WalletNotification, WalletNotificationEvent, WalletRequest, WalletResponse, WalletResponseEvent, WalletSupport } from "./helpers/index.js";
6
+ import { NostrConnectionMethodsOptions, NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./interop.js";
7
7
  export type SerializedWalletConnect = WalletConnectURI;
8
- export type WalletConnectOptions = {
8
+ export type WalletConnectOptions = NostrConnectionMethodsOptions & {
9
9
  /** The secret to use for the connection */
10
10
  secret: Uint8Array;
11
11
  /** The relays to use for the connection */
@@ -14,12 +14,6 @@ export type WalletConnectOptions = {
14
14
  service?: string;
15
15
  /** Default timeout for RPC requests in milliseconds */
16
16
  timeout?: number;
17
- /** A method for subscribing to relays */
18
- subscriptionMethod?: NostrSubscriptionMethod;
19
- /** A method for publishing events */
20
- publishMethod?: NostrPublishMethod;
21
- /** An optional pool for connection methods */
22
- pool?: NostrPool;
23
17
  };
24
18
  export declare class WalletConnect {
25
19
  /** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
@@ -54,9 +48,9 @@ export declare class WalletConnect {
54
48
  protected waitForService$: Observable<string>;
55
49
  constructor(options: WalletConnectOptions);
56
50
  /** Process response events and return WalletResponse or throw error */
57
- protected handleResponseEvent(event: NostrEvent, encryption?: EncryptionMethod): Promise<WalletResponse>;
51
+ protected handleResponseEvent(event: WalletResponseEvent, encryption?: EncryptionMethod): Promise<WalletResponse>;
58
52
  /** Handle notification events */
59
- protected handleNotificationEvent(event: NostrEvent): Promise<WalletNotification>;
53
+ protected handleNotificationEvent(event: WalletNotificationEvent): Promise<WalletNotification>;
60
54
  /** Core RPC method that makes a request and returns the response */
61
55
  request(request: WalletRequest, options?: {
62
56
  timeout?: number;
@@ -2,10 +2,11 @@ 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, ReplaySubject, share, switchMap, takeUntil, tap, timer, toArray, } from "rxjs";
5
+ import { BehaviorSubject, 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
- 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";
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";
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;
@@ -58,15 +59,7 @@ export class WalletConnect {
58
59
  },
59
60
  };
60
61
  // Get the subscription and publish methods
61
- const subscriptionMethod = options.subscriptionMethod ||
62
- options.pool?.subscription ||
63
- WalletConnect.subscriptionMethod ||
64
- WalletConnect.pool?.subscription;
65
- if (!subscriptionMethod)
66
- throw new Error("Missing subscriptionMethod, either pass a method or set WalletConnect.subscriptionMethod");
67
- const publishMethod = options.publishMethod || options.pool?.publish || WalletConnect.publishMethod || WalletConnect.pool?.publish;
68
- if (!publishMethod)
69
- throw new Error("Missing publishMethod, either pass a method or set WalletConnect.publishMethod");
62
+ const { subscriptionMethod, publishMethod } = getConnectionMethods(options, WalletConnect);
70
63
  // Use arrow functions so "this" isn't bound to the signer
71
64
  this.subscriptionMethod = (relays, filters) => subscriptionMethod(relays, filters);
72
65
  this.publishMethod = (relays, event) => publishMethod(relays, event);
@@ -75,10 +68,14 @@ export class WalletConnect {
75
68
  const client = getPublicKey(this.secret);
76
69
  // If the service is not known yet, subscribe to a wallet info event tagging the client
77
70
  if (!service)
78
- return this.subscriptionMethod(this.relays, [{ kinds: [WALLET_INFO_KIND], "#p": [client] }]).pipe(
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(),
79
76
  // Ignore strings (support for applesauce-relay)
80
77
  filter((event) => typeof event !== "string"));
81
- return this.subscriptionMethod(this.relays, [
78
+ return from(this.subscriptionMethod(this.relays, [
82
79
  // Subscribe to response events
83
80
  {
84
81
  kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
@@ -87,7 +84,11 @@ export class WalletConnect {
87
84
  },
88
85
  // Subscribe to wallet info events
89
86
  { kinds: [WALLET_INFO_KIND], authors: [service] },
90
- ]).pipe(
87
+ ])).pipe(
88
+ // Keep the connection open indefinitely
89
+ repeat(),
90
+ // Retry on connection failure
91
+ retry(),
91
92
  // Ignore strings (support for applesauce-relay)
92
93
  filter((event) => typeof event !== "string"),
93
94
  // Only include events from the wallet service
@@ -102,19 +103,24 @@ export class WalletConnect {
102
103
  resetOnRefCountZero: () => timer(60000), // Keep info observable around for 1 minute after last unsubscribe
103
104
  }));
104
105
  this.encryption$ = this.support$.pipe(map((info) => (info ? getPreferredEncryption(info) : "nip04")));
105
- this.notifications$ = this.events$.pipe(filter((event) => event.kind === WALLET_NOTIFICATION_KIND), mergeMap((event) => this.handleNotificationEvent(event)));
106
- this.waitForService$ = this.events$.pipe(
107
- // Complete when the service is set
108
- takeUntil(this.service$),
109
- // Only listen for wallet info events
110
- filter((event) => event.kind === WALLET_INFO_KIND && !this.service),
111
- // Set the service to the pubkey of the wallet info event
112
- tap((event) => {
113
- // Set the service to the pubkey of the wallet info event
114
- this.service$.next(event.pubkey);
115
- }),
116
- // Get the service pubkey from the event
117
- map((event) => event.pubkey),
106
+ this.notifications$ = this.events$.pipe(filter((event) => isValidWalletNotification(event)), mergeMap((event) => this.handleNotificationEvent(event)));
107
+ this.waitForService$ = defer(() =>
108
+ // If service is already set, return it
109
+ this.service$.value
110
+ ? of(this.service$.value)
111
+ : // Otherwise listen for new wallet info events
112
+ this.events$.pipe(
113
+ // Only listen for wallet info events
114
+ filter((event) => event.kind === WALLET_INFO_KIND),
115
+ // Set the service to the pubkey of the wallet info event
116
+ tap((event) => {
117
+ // Set the service to the pubkey of the wallet info event
118
+ this.service$.next(event.pubkey);
119
+ }),
120
+ // Get the service pubkey from the event
121
+ map((event) => event.pubkey),
122
+ // Complete after the first value
123
+ take(1))).pipe(
118
124
  // Only create a single subscription to avoid multiple side effects
119
125
  share());
120
126
  }
@@ -125,11 +131,7 @@ export class WalletConnect {
125
131
  const requestId = getWalletResponseRequestId(event);
126
132
  if (!requestId)
127
133
  throw new Error("Response missing request ID");
128
- let response;
129
- if (isWalletResponseLocked(event))
130
- response = await unlockWalletResponse(event, this.signer, encryption);
131
- else
132
- response = getWalletResponse(event);
134
+ const response = await unlockWalletResponse(event, this.signer, encryption);
133
135
  if (!response)
134
136
  throw new Error("Failed to decrypt or parse response");
135
137
  return response;
@@ -138,11 +140,7 @@ export class WalletConnect {
138
140
  async handleNotificationEvent(event) {
139
141
  if (!verifyEvent(event))
140
142
  throw new Error("Invalid notification event signature");
141
- let notification;
142
- if (isWalletNotificationLocked(event))
143
- notification = await unlockWalletNotification(event, this.signer);
144
- else
145
- notification = getWalletNotification(event);
143
+ const notification = await unlockWalletNotification(event, this.signer);
146
144
  if (!notification)
147
145
  throw new Error("Failed to decrypt or parse notification");
148
146
  return notification;
@@ -166,7 +164,7 @@ export class WalletConnect {
166
164
  // Create an observable that publishes the request event when subscribed to
167
165
  const request$ = defer(() => from(this.publishMethod(this.relays, requestEvent))).pipe(ignoreElements());
168
166
  // Create an observable that listens for response events
169
- const responses$ = this.events$.pipe(filter((response) => response.kind === WALLET_RESPONSE_KIND && getWalletResponseRequestId(response) === requestEvent.id), mergeMap((response) => this.handleResponseEvent(response, encryption)),
167
+ const responses$ = this.events$.pipe(filter(isValidWalletResponse), filter((response) => getWalletResponseRequestId(response) === requestEvent.id), mergeMap((response) => this.handleResponseEvent(response, encryption)),
170
168
  // Set timeout for response events
171
169
  simpleTimeout(options.timeout || this.defaultTimeout));
172
170
  return merge(request$, responses$);
@@ -1,13 +1,12 @@
1
1
  import { EventSigner } from "applesauce-factory";
2
- import { NostrEvent } from "nostr-tools";
3
2
  import { Observable, Subscription } from "rxjs";
3
+ import { WalletAuthURI } from "./helpers/auth-uri.js";
4
4
  import { WalletErrorCode } from "./helpers/error.js";
5
5
  import { NotificationType, WalletNotification } from "./helpers/notification.js";
6
- import { GetBalanceParams, GetInfoParams, ListTransactionsParams, LookupInvoiceParams, MakeInvoiceParams, MultiPayInvoiceParams, MultiPayKeysendParams, PayInvoiceParams, PayKeysendParams, WalletRequest } from "./helpers/request.js";
6
+ import { GetBalanceParams, GetInfoParams, ListTransactionsParams, LookupInvoiceParams, MakeInvoiceParams, MultiPayInvoiceParams, MultiPayKeysendParams, PayInvoiceParams, PayKeysendParams, WalletRequest, WalletRequestEvent } from "./helpers/request.js";
7
7
  import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceResult, MultiPayInvoiceResult, MultiPayKeysendResult, PayInvoiceResult, PayKeysendResult, WalletResponse } from "./helpers/response.js";
8
8
  import { WalletSupport } from "./helpers/support.js";
9
- import { NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./types.js";
10
- import { WalletAuthURI } from "./helpers/auth-uri.js";
9
+ import { NostrConnectionMethodsOptions, NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./interop.js";
11
10
  /** Handler function for pay_invoice method */
12
11
  export type PayInvoiceHandler = (params: PayInvoiceParams) => Promise<PayInvoiceResult>;
13
12
  /** Handler function for multi_pay_invoice method */
@@ -45,7 +44,7 @@ export type SerializedWalletService = {
45
44
  relays: string[];
46
45
  };
47
46
  /** Options for creating a WalletService */
48
- export interface WalletServiceOptions {
47
+ export interface WalletServiceOptions extends NostrConnectionMethodsOptions {
49
48
  /** The relays to use for the service */
50
49
  relays: string[];
51
50
  /** The signer to use for creating and unlocking events */
@@ -58,12 +57,6 @@ export interface WalletServiceOptions {
58
57
  handlers: WalletServiceHandlers;
59
58
  /** An array of notifications this wallet supports */
60
59
  notifications?: NotificationType[];
61
- /** An optional method for subscribing to relays */
62
- subscriptionMethod?: NostrSubscriptionMethod;
63
- /** An optional method for publishing events */
64
- publishMethod?: NostrPublishMethod;
65
- /** An optional pool for connection methods */
66
- pool?: NostrPool;
67
60
  }
68
61
  /** NIP-47 Wallet Service implementation */
69
62
  export declare class WalletService {
@@ -89,11 +82,11 @@ export declare class WalletService {
89
82
  /** The service's public key */
90
83
  pubkey: string | null;
91
84
  /** The client's secret key */
92
- protected secret: Uint8Array;
85
+ protected secret?: Uint8Array;
93
86
  /** The client's public key */
94
87
  client: string;
95
88
  /** Shared observable for all wallet request events */
96
- protected events$: Observable<NostrEvent> | null;
89
+ protected events$: Observable<WalletRequestEvent> | null;
97
90
  /** Subscription to the events observable */
98
91
  protected subscription: Subscription | null;
99
92
  /** Whether the service is currently running */
@@ -112,15 +105,15 @@ export declare class WalletService {
112
105
  /** Publish the wallet support event */
113
106
  protected publishSupportEvent(): Promise<void>;
114
107
  /** Handle a wallet request event */
115
- protected handleRequestEvent(requestEvent: NostrEvent): Promise<void>;
108
+ protected handleRequestEvent(requestEvent: WalletRequestEvent): Promise<void>;
116
109
  /** Process a decrypted wallet request */
117
- protected processRequest(requestEvent: NostrEvent, request: WalletRequest): Promise<void>;
110
+ protected processRequest(requestEvent: WalletRequestEvent, request: WalletRequest): Promise<void>;
118
111
  /** Send a success response */
119
- protected sendSuccessResponse<T extends WalletResponse>(requestEvent: NostrEvent, method: T["result_type"], result: T["result"]): Promise<void>;
112
+ protected sendSuccessResponse<T extends WalletResponse>(requestEvent: WalletRequestEvent, method: T["result_type"], result: T["result"]): Promise<void>;
120
113
  /** Send an error response */
121
- protected sendErrorResponse(requestEvent: NostrEvent, errorType: WalletErrorCode, errorMessage: string): Promise<void>;
114
+ protected sendErrorResponse(requestEvent: WalletRequestEvent, errorType: WalletErrorCode, errorMessage: string): Promise<void>;
122
115
  /** Send a response event */
123
- protected sendResponse(requestEvent: NostrEvent, response: WalletResponse): Promise<void>;
116
+ protected sendResponse(requestEvent: WalletRequestEvent, response: WalletResponse): Promise<void>;
124
117
  /** Creates a service for a nostr+walletauth URI */
125
118
  static fromAuthURI(uri: string | WalletAuthURI, options: Omit<WalletServiceOptions, "relays">): WalletService;
126
119
  }
@@ -1,14 +1,15 @@
1
1
  import { logger } from "applesauce-core";
2
2
  import { create } from "applesauce-factory";
3
3
  import { generateSecretKey, getPublicKey, verifyEvent } from "nostr-tools";
4
- import { filter, mergeMap, share } from "rxjs";
4
+ import { filter, from, mergeMap, repeat, retry, share } from "rxjs";
5
+ import { bytesToHex } from "@noble/hashes/utils";
5
6
  import { WalletLegacyNotificationBlueprint, WalletNotificationBlueprint } from "./blueprints/notification.js";
6
7
  import { WalletResponseBlueprint } from "./blueprints/response.js";
7
8
  import { WalletSupportBlueprint } from "./blueprints/support.js";
8
- import { WalletBaseError } from "./helpers/error.js";
9
- import { getWalletRequest, isWalletRequestExpired, isWalletRequestLocked, unlockWalletRequest, WALLET_REQUEST_KIND, } from "./helpers/request.js";
10
- import { bytesToHex } from "@noble/hashes/utils";
11
9
  import { parseWalletAuthURI } from "./helpers/auth-uri.js";
10
+ import { WalletBaseError } from "./helpers/error.js";
11
+ import { getWalletRequest, isValidWalletRequest, isWalletRequestExpired, unlockWalletRequest, WALLET_REQUEST_KIND, } from "./helpers/request.js";
12
+ import { getConnectionMethods, } from "./interop.js";
12
13
  /** NIP-47 Wallet Service implementation */
13
14
  export class WalletService {
14
15
  /** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
@@ -48,23 +49,21 @@ export class WalletService {
48
49
  this.handlers = options.handlers;
49
50
  // Set the client's secret and public key
50
51
  if (options.secret) {
52
+ // Service was created with a custom secret
51
53
  this.secret = options.secret;
52
54
  this.client = getPublicKey(this.secret);
53
55
  }
56
+ else if (options.client) {
57
+ // Service was restored with only the clients pubkey
58
+ this.client = options.client;
59
+ }
54
60
  else {
61
+ // Generate secret and client pubkey
55
62
  this.secret = generateSecretKey();
56
63
  this.client = getPublicKey(this.secret);
57
64
  }
58
65
  // Get the subscription and publish methods
59
- const subscriptionMethod = options.subscriptionMethod ||
60
- options.pool?.subscription ||
61
- WalletService.subscriptionMethod ||
62
- WalletService.pool?.subscription;
63
- if (!subscriptionMethod)
64
- throw new Error("Missing subscriptionMethod, either pass a method or set WalletService.subscriptionMethod");
65
- const publishMethod = options.publishMethod || options.pool?.publish || WalletService.publishMethod || WalletService.pool?.publish;
66
- if (!publishMethod)
67
- throw new Error("Missing publishMethod, either pass a method or set WalletService.publishMethod");
66
+ const { subscriptionMethod, publishMethod } = getConnectionMethods(options, WalletService);
68
67
  // Use arrow functions so "this" isn't bound to the signer
69
68
  this.subscriptionMethod = (relays, filters) => subscriptionMethod(relays, filters);
70
69
  this.publishMethod = (relays, event) => publishMethod(relays, event);
@@ -91,17 +90,23 @@ export class WalletService {
91
90
  // Get our public key
92
91
  this.pubkey = await this.signer.getPublicKey();
93
92
  // Create shared request observable with ref counting and timer
94
- this.events$ = this.subscriptionMethod(this.relays, [
93
+ this.events$ = from(this.subscriptionMethod(this.relays, [
95
94
  {
96
95
  kinds: [WALLET_REQUEST_KIND],
97
96
  "#p": [this.pubkey], // Only requests directed to us
98
97
  authors: [this.client], // Only requests from the client
99
98
  },
100
- ]).pipe(
99
+ ])).pipe(
100
+ // Keep the connection open indefinitely
101
+ repeat(),
102
+ // Retry on connection failure
103
+ retry(),
101
104
  // Ignore strings (support for applesauce-relay)
102
105
  filter((event) => typeof event !== "string"),
103
106
  // Only include valid wallet request events
104
- filter((event) => event.kind === WALLET_REQUEST_KIND && event.pubkey === this.client),
107
+ filter(isValidWalletRequest),
108
+ // Ensure they are to our pubkey
109
+ filter((event) => event.pubkey === this.client),
105
110
  // Verify event signature
106
111
  filter((event) => verifyEvent(event)),
107
112
  // Only create a single subscription to the relays
@@ -132,6 +137,8 @@ export class WalletService {
132
137
  }
133
138
  /** Get the connection URI for the service */
134
139
  getConnectURI() {
140
+ if (!this.secret)
141
+ throw new Error("Service was not created with a secret");
135
142
  if (!this.pubkey)
136
143
  throw new Error("Service is not running");
137
144
  if (!this.relays.length)
@@ -171,13 +178,7 @@ export class WalletService {
171
178
  if (isWalletRequestExpired(requestEvent))
172
179
  return await this.sendErrorResponse(requestEvent, "OTHER", "Request has expired");
173
180
  // Unlock the request if needed
174
- let request;
175
- if (isWalletRequestLocked(requestEvent)) {
176
- request = await unlockWalletRequest(requestEvent, this.signer);
177
- }
178
- else {
179
- request = getWalletRequest(requestEvent);
180
- }
181
+ const request = await unlockWalletRequest(requestEvent, this.signer);
181
182
  if (!request)
182
183
  return await this.sendErrorResponse(requestEvent, "OTHER", "Failed to decrypt or parse request");
183
184
  // Handle the request based on its method
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-wallet-connect",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,23 +58,25 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "applesauce-core": "^3.0.0",
62
- "applesauce-factory": "^3.0.0",
63
- "nostr-tools": "^2.13",
64
61
  "@noble/hashes": "^1.7.1",
62
+ "applesauce-core": "^4.0.0",
63
+ "applesauce-factory": "^4.0.0",
64
+ "nostr-tools": "~2.17",
65
65
  "rxjs": "^7.8.1"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@hirez_io/observer-spy": "^2.2.0",
69
69
  "@types/debug": "^4.1.12",
70
+ "rimraf": "^6.0.1",
70
71
  "typescript": "^5.8.3",
71
- "vitest": "^3.2.3"
72
+ "vitest": "^3.2.4"
72
73
  },
73
74
  "funding": {
74
75
  "type": "lightning",
75
76
  "url": "lightning:nostrudel@geyser.fund"
76
77
  },
77
78
  "scripts": {
79
+ "prebuild": "rimraf dist",
78
80
  "build": "tsc",
79
81
  "watch:build": "tsc --watch > /dev/null",
80
82
  "test": "vitest run --passWithNoTests",
package/dist/types.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { Filter, NostrEvent } from "nostr-tools";
2
- import { Observable } from "rxjs";
3
- /** A method used to subscribe to events on a set of relays */
4
- export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Observable<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> | 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
- };
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};