Files
app.zeusln.zeus/stores/LnurlPayStore.ts
T

217 lines
6.4 KiB
TypeScript

import { action, runInAction } from 'mobx';
import { LNURLPaySuccessAction } from 'js-lnurl';
import { schnorr } from '@noble/curves/secp256k1';
import { hexToBytes } from '@noble/hashes/utils';
import hashjs from 'hash.js';
import {
nip19,
// @ts-ignore:next-line
finishEvent,
// @ts-ignore:next-line
generatePrivateKey,
// @ts-ignore:next-line
getPublicKey,
// @ts-ignore:next-line
relayInit
} from 'nostr-tools';
import Storage from '../storage';
import SettingsStore from './SettingsStore';
import NodeInfoStore from './NodeInfoStore';
export interface LnurlPayTransaction {
paymentHash: string;
domain: string;
lnurl: string;
metadata_hash: string;
successAction: LNURLPaySuccessAction;
time: number;
metadata?: Metadata; // only after an independent load from Storage.
}
interface Metadata {
metadata: string;
}
interface LnurlPayMetadataEntry {
metadata: string;
last_stored: number;
}
export default class LnurlPayStore {
paymentHash: string | undefined;
domain: string | undefined;
successAction: LNURLPaySuccessAction | undefined;
settingsStore: SettingsStore;
nodeInfoStore: NodeInfoStore;
// Zaplocker
isZaplocker: boolean | undefined;
isPmtHashSigValid: boolean | undefined;
isRelaysSigValid: boolean | undefined;
zaplockerNpub: string | undefined;
relays: Array<string> | undefined;
paymentRequest: string | undefined;
lightningAddress: string | undefined;
constructor(settingsStore: SettingsStore, nodeInfoStore: NodeInfoStore) {
this.settingsStore = settingsStore;
this.nodeInfoStore = nodeInfoStore;
}
@action
public reset = () => {
this.paymentHash = undefined;
this.domain = undefined;
this.successAction = undefined;
this.isZaplocker = undefined;
this.isPmtHashSigValid = undefined;
this.isRelaysSigValid = undefined;
this.zaplockerNpub = undefined;
this.relays = undefined;
this.paymentRequest = undefined;
this.lightningAddress = undefined;
};
public load = async (paymentHash: string): Promise<LnurlPayTransaction> => {
let lnurlpaytx: any = await Storage.getItem('lnurlpay:' + paymentHash);
if (lnurlpaytx) {
lnurlpaytx = JSON.parse(lnurlpaytx);
const metadata: any = await Storage.getItem(
'lnurlpay:' + lnurlpaytx.metadata_hash
);
if (metadata) {
lnurlpaytx.metadata = JSON.parse(metadata);
}
}
return lnurlpaytx;
};
public keep = async (
paymentHash: string,
domain: string,
lnurl: string,
metadata: string,
descriptionHash: string,
successAction: LNURLPaySuccessAction,
pmthash_sig?: string,
user_pubkey?: string,
relays?: Array<string>,
relays_sig?: string,
pr?: string,
lightningAddress?: string
) => {
this.reset();
const now = new Date().getTime();
const transactionData: LnurlPayTransaction = {
paymentHash,
domain,
lnurl,
successAction,
time: now,
metadata_hash: descriptionHash
};
const metadataEntry: LnurlPayMetadataEntry = {
metadata,
last_stored: now
};
await Storage.setItem('lnurlpay:' + paymentHash, transactionData);
await Storage.setItem('lnurlpay:' + descriptionHash, metadataEntry);
runInAction(() => {
this.paymentHash = paymentHash;
this.successAction = successAction;
this.domain = domain;
if (pr) this.paymentRequest = pr;
if (lightningAddress) this.lightningAddress = lightningAddress;
// Zaplocker
if (user_pubkey) {
this.isZaplocker = true;
try {
this.zaplockerNpub = nip19.npubEncode(user_pubkey);
} catch (e) {}
if (pmthash_sig) {
const pmtHashBytes = hexToBytes(pmthash_sig);
this.isPmtHashSigValid = schnorr.verify(
pmtHashBytes,
paymentHash,
user_pubkey
);
}
if (relays && relays_sig) {
this.relays = relays;
const relaysBytes = hexToBytes(relays_sig);
this.isRelaysSigValid = schnorr.verify(
relaysBytes,
hashjs
.sha256()
.update(JSON.stringify(relays))
.digest('hex'),
user_pubkey
);
}
}
});
};
public broadcastAttestation = async () => {
const hash = this.paymentHash;
const invoice = this.paymentRequest;
const relays = this.relays;
if (!hash || !invoice || !relays) return;
// create ephemeral key
const sk = generatePrivateKey();
const pk = getPublicKey(sk);
const hashpk = getPublicKey(hash);
const event = {
kind: 55869,
pubkey: pk,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', hashpk]],
content: invoice
};
// this calculates the event id and signs the event in a single step
const signedEvent = finishEvent(event, sk);
console.log('signedEvent', signedEvent);
await Promise.all(
relays.map(async (relayItem: string) => {
const relay = relayInit(relayItem);
relay.on('connect', () => {
console.log(`connected to ${relay.url}`);
});
relay.on('error', () => {
console.log(`failed to connect to ${relay.url}`);
});
await relay.connect();
await relay.publish(signedEvent);
console.log('event.id', signedEvent.id);
const eventReceived = await relay.get({
ids: [signedEvent.id]
});
console.log('eventReceived', eventReceived);
return;
})
);
console.log('broadcast complete');
return;
};
}