Skip to content

Commit

Permalink
feat: multi keysend (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Feb 2, 2024
1 parent 87b8e2d commit dc6faf8
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 18 deletions.
12 changes: 5 additions & 7 deletions examples/nwc/keysend.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,19 @@ const rl = readline.createInterface({ input, output });
const nwcUrl =
process.env.NWC_URL ||
(await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): "));
const destination =
(await rl.question("Enter destination pubkey: ")) ||
"030a58b8653d32b99200a2334cfe913e51dc7d155aa0116c176657a4f1722677a3";
const amount = await rl.question("Enter amount: ");

rl.close();

const webln = new providers.NostrWebLNProvider({
nostrWalletConnectUrl: nwcUrl,
});
await webln.enable();
const response = await webln.keysend({
amount,
destination,
amount: 1,
destination:
"030a58b8653d32b99200a2334cfe913e51dc7d155aa0116c176657a4f1722677a3",
customRecords: {
696969: "1KOZHzhLs2U7JIx3BmEY",
696969: "017rsl75kNnSke4mMHYE", // [email protected]
},
});

Expand Down
48 changes: 48 additions & 0 deletions examples/nwc/multi-keysend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as crypto from "node:crypto"; // required in node.js
global.crypto = crypto; // required in node.js
import "websocket-polyfill"; // required in node.js

import * as readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { webln as providers } from "../../dist/index.module.js";

const rl = readline.createInterface({ input, output });

const nwcUrl =
process.env.NWC_URL ||
(await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): "));
rl.close();

const webln = new providers.NostrWebLNProvider({
nostrWalletConnectUrl: nwcUrl,
});
await webln.enable();

const keysends = [
{
destination:
"030a58b8653d32b99200a2334cfe913e51dc7d155aa0116c176657a4f1722677a3",
amount: 1,
customRecords: {
696969: "017rsl75kNnSke4mMHYE", // [email protected]
},
},
{
destination:
"030a58b8653d32b99200a2334cfe913e51dc7d155aa0116c176657a4f1722677a3",
amount: 1,
customRecords: {
696969: "1KOZHzhLs2U7JIx3BmEY", // another Alby account
},
},
];

try {
const response = await webln.multiKeysend(keysends);
console.info(response);
} catch (error) {
console.error("multiKeysend failed", error);
}

webln.close();
70 changes: 59 additions & 11 deletions src/webln/NostrWeblnProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export type SendMultiPaymentResponse = {
errors: { paymentRequest: string; message: string }[];
};

// TODO: consider moving to webln-types package
export type MultiKeysendResponse = {
keysends: ({ keysend: KeysendArgs } & SendPaymentResponse)[];
errors: { keysend: KeysendArgs; message: string }[];
};

interface Nip47ListTransactionsArgs {
from?: number;
until?: number;
Expand Down Expand Up @@ -117,6 +123,7 @@ const nip47ToWeblnRequestMap = {
};
const nip47ToWeblnMultiRequestMap = {
multi_pay_invoice: "sendMultiPayment",
multi_pay_keysend: "multiKeysend",
};

export class NostrWebLNProvider implements WebLNProvider, Nip07Provider {
Expand Down Expand Up @@ -381,6 +388,7 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider {

return {
payments: results,
// TODO: error handling
errors: [],
};
}
Expand All @@ -390,21 +398,46 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider {

return this.executeNip47Request<SendPaymentResponse, Nip47PayResponse>(
"pay_keysend",
mapKeysendToNip47Keysend(args),
(result) => !!result.preimage,
(result) => ({ preimage: result.preimage }),
);
}

// NOTE: this method may change - it has not been proposed to be added to the WebLN spec yet.
async multiKeysend(keysends: KeysendArgs[]): Promise<MultiKeysendResponse> {
await this.checkConnected();

const results = await this.executeMultiNip47Request<
{ preimage: string; keysend: KeysendArgs },
Nip47PayResponse
>(
"multi_pay_keysend",
{
amount: +args.amount * 1000, // NIP-47 uses msat
pubkey: args.destination,
tlv_records: args.customRecords
? Object.entries(args.customRecords).map((v) => ({
type: parseInt(v[0]),
value: v[1],
}))
: [],
// TODO: support optional preimage
// preimage?: "123",
keysends: keysends.map((keysend, index) => ({
...mapKeysendToNip47Keysend(keysend),
id: index.toString(),
})),
},
keysends.length,
(result) => !!result.preimage,
(result) => ({ preimage: result.preimage }),
(result) => {
const keysend = keysends[parseInt(result.dTag)];
if (!keysend) {
throw new Error("Could not find keysend matching response d tag");
}
return {
keysend,
preimage: result.preimage,
};
},
);

return {
keysends: results,
// TODO: error handling
errors: [],
};
}

// not-yet implemented WebLN interface methods
Expand Down Expand Up @@ -837,4 +870,19 @@ function mapNip47TransactionToTransaction(
};
}

function mapKeysendToNip47Keysend(args: KeysendArgs) {
return {
amount: +args.amount * 1000, // NIP-47 uses msat
pubkey: args.destination,
tlv_records: args.customRecords
? Object.entries(args.customRecords).map((v) => ({
type: parseInt(v[0]),
value: v[1],
}))
: [],
// TODO: support optional preimage
// preimage?: "123",
};
}

export const NWC = NostrWebLNProvider;

0 comments on commit dc6faf8

Please sign in to comment.