Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nwc): add cross-currency nwc methods #251

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 159 additions & 4 deletions src/NWCClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ type Nip47SingleMethod =
| "pay_keysend"
| "lookup_invoice"
| "list_transactions"
| "sign_message";
| "sign_message"
| "lookup_user"
| "fetch_quote"
| "execute_quote"
| "pay_to_address";

type Nip47MultiMethod = "multi_pay_invoice" | "multi_pay_keysend";

Expand All @@ -44,11 +48,19 @@ export type Nip47GetInfoResponse = {
block_height: number;
block_hash: string;
methods: Nip47Method[];

notifications?: Nip47NotificationType[];
lud16?: string;
currencies?: Nip47Currency[];
};

export type Nip47GetBalanceRequest = {
currency_code?: string;
};

export type Nip47GetBalanceResponse = {
balance: number; // msats
balance: number; // msats or currency if specified.
currency_code?: string;
};

export type Nip47PayResponse = {
Expand Down Expand Up @@ -87,6 +99,16 @@ export type Nip47ListTransactionsResponse = {
transactions: Nip47Transaction[];
};

export type Nip47TransactionFx = {
receiving_currency_code: string;
total_receiving_amount: number;
multiplier: number;
// Remaining fields only available for outgoing payments:
sending_currency_code?: string;
total_sending_amount?: number;
fees?: number;
};

export type Nip47Transaction = {
type: string;
invoice: string;
Expand All @@ -99,9 +121,20 @@ export type Nip47Transaction = {
settled_at: number;
created_at: number;
expires_at: number;
fx?: Nip47TransactionFx;
metadata?: Record<string, unknown>;
};

export type Nip47Currency = {
code: string;
name: string;
symbol: string;
multiplier: number;
min: number;
max: number;
decimals: number;
};

export type Nip47NotificationType = Nip47Notification["notification_type"];

export type Nip47Notification =
Expand Down Expand Up @@ -148,6 +181,60 @@ export type Nip47SignMessageResponse = {
signature: string;
};

// Note: It is expected that exactly one of the fields is set.
export type Nip47Receiver = {
lud16?: string;
bolt12?: string;
};

export type Nip47LookupUserRequest = {
lud16: string;
};

export type Nip47LookupUserResponse = {
lud16: string;
currencies: Nip47Currency[];
};

export type Nip47FetchQuoteRequest = {
receiver: Nip47Receiver;
sending_currency_code: string;
receiving_currency_code: string;
locked_currency_side: "SENDING" | "RECEIVING";
locked_currency_amount: number;
};

export type Nip47Quote = {
payment_hash: string;
sending_currency_code: string;
receiving_currency_code: string;
locked_currency_side: "SENDING" | "RECEIVING";
total_receiving_amount: number;
total_sending_amount: number;
multiplier: number; // receiving unit per sending unit
fees: number; // in sending currency
expires_at: number;
};

export type Nip47ExecuteQuoteRequest = {
payment_hash: string;
};

export type Nip47ExecuteQuoteResponse = {
preimage: string;
};

export type Nip47PayToAddressRequest = {
receiver: Nip47Receiver;
sending_currency_code: string;
sending_currency_amount: number;
receiving_currency_code?: string;
};

export type Nip47PayToAddressResponse = {
preimage: string;
};

export interface NWCOptions {
authorizationUrl?: string; // the URL to the NWC interface for the user to confirm the session
relayUrl: string;
Expand Down Expand Up @@ -490,11 +577,13 @@ export class NWCClient {
}
}

async getBalance(): Promise<Nip47GetBalanceResponse> {
async getBalance(
request: Nip47GetBalanceRequest = {},
): Promise<Nip47GetBalanceResponse> {
try {
const result = await this.executeNip47Request<Nip47GetBalanceResponse>(
"get_balance",
{},
request,
(result) => result.balance !== undefined,
);
return result;
Expand Down Expand Up @@ -654,6 +743,72 @@ export class NWCClient {
}
}

async lookupUser(
request: Nip47LookupUserRequest,
): Promise<Nip47LookupUserResponse> {
try {
const result = await this.executeNip47Request<Nip47LookupUserResponse>(
"lookup_user",
request,
(response) => !!response.currencies,
);

return result;
} catch (error) {
console.error("Failed to request lookup_user", error);
throw error;
}
}

async fetchQuote(request: Nip47FetchQuoteRequest): Promise<Nip47Quote> {
try {
const result = await this.executeNip47Request<Nip47Quote>(
"fetch_quote",
request,
(response) => !!response.multiplier,
);

return result;
} catch (error) {
console.error("Failed to request fetch_quote", error);
throw error;
}
}

async executeQuote(
request: Nip47ExecuteQuoteRequest,
): Promise<Nip47ExecuteQuoteResponse> {
try {
const result = await this.executeNip47Request<Nip47ExecuteQuoteResponse>(
"execute_quote",
request,
(response) => !!response.preimage,
);

return result;
} catch (error) {
console.error("Failed to request execute_quote", error);
throw error;
}
}

async payToAddress(
request: Nip47PayToAddressRequest,
): Promise<Nip47PayToAddressResponse> {
try {
const result = await this.executeNip47Request<Nip47PayToAddressResponse>(
"pay_to_address",
request,
(response) => !!response.preimage,
);

return result;
} catch (error) {
console.error("Failed to request pay_to_address", error);
throw error;
}
}

async subscribeNotifications(
onNotification: (notification: Nip47Notification) => void,
notificationTypes?: Nip47NotificationType[],
Expand Down
4 changes: 4 additions & 0 deletions src/webln/NostrWeblnProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ const nip47ToWeblnRequestMap: Record<Nip47Method, WebLNMethod> = {
multi_pay_invoice: "sendMultiPayment",
multi_pay_keysend: "multiKeysend",
sign_message: "signMessage",
lookup_user: "lookupUser",
fetch_quote: "fetchQuote",
execute_quote: "executeQuote",
pay_to_address: "payToAddress",
};

export class NostrWebLNProvider implements WebLNProvider, Nip07Provider {
Expand Down