Skip to content

Commit

Permalink
publish order on nostr (#516)
Browse files Browse the repository at this point in the history
* publish order on nostr

Refactoring old nostr code to publish orders on nostr

* Generate random secret key in case we needed

* Add RELAYS env var

* Fix locales messages

* Add community id tag to event
  • Loading branch information
grunch authored May 11, 2024
1 parent 45f1122 commit fe1d187
Show file tree
Hide file tree
Showing 27 changed files with 216 additions and 195 deletions.
5 changes: 4 additions & 1 deletion .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ COMMUNITY_TTL=31
NOSTR_SK=''

# Number of currencies allowed in a community
COMMUNITY_CURRENCIES=20
COMMUNITY_CURRENCIES=20

# List of relays to connect to
RELAYS='ws://localhost:7000,ws://localhost:8000,ws://localhost:9000'
8 changes: 8 additions & 0 deletions bot/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
getFee,
} = require('../util');
const ordersActions = require('./ordersActions');
const OrderEvents = require('./modules/events/orders');

const { resolvLightningAddress } = require('../lnurl/lnurl-pay');
const { logger } = require('../logger');
Expand Down Expand Up @@ -81,6 +82,8 @@ const waitPayment = async (ctx, bot, buyer, seller, order, buyerInvoice) => {
await messages.takeSellWaitingSellerToPayMessage(ctx, bot, buyer, order);
}
await order.save();
// We update the nostr event
OrderEvents.orderUpdated(order);
} catch (error) {
logger.error(`Error in waitPayment: ${error}`);
}
Expand Down Expand Up @@ -330,6 +333,7 @@ const cancelAddInvoice = async (ctx, order, job) => {
} else {
await messages.successCancelOrderMessage(ctx, user, order, i18nCtx);
}
OrderEvents.orderUpdated(order);
}
} catch (error) {
logger.error(error);
Expand Down Expand Up @@ -503,6 +507,7 @@ const cancelShowHoldInvoice = async (ctx, order, job) => {
} else {
await messages.successCancelOrderMessage(ctx, user, order, i18nCtx);
}
OrderEvents.orderUpdated(order);
}
} catch (error) {
logger.error(error);
Expand Down Expand Up @@ -561,6 +566,7 @@ const cancelOrder = async (ctx, orderId, user) => {
order.status = 'CANCELED';
order.canceled_by = user._id;
await order.save();
OrderEvents.orderUpdated(order);
// we sent a private message to the user
await messages.successCancelOrderMessage(ctx, user, order, ctx.i18n);
// We delete the messages related to that order from the channel
Expand Down Expand Up @@ -651,6 +657,7 @@ const cancelOrder = async (ctx, orderId, user) => {
);
}
await order.save();
OrderEvents.orderUpdated(order);
} catch (error) {
logger.error(error);
}
Expand All @@ -674,6 +681,7 @@ const fiatSent = async (ctx, orderId, user) => {
order.status = 'FIAT_SENT';
const seller = await User.findOne({ _id: order.seller_id });
await order.save();
OrderEvents.orderUpdated(order);
// We sent messages to both parties
// We need to create i18n context for each user
const i18nCtxBuyer = await getUserI18nContext(user);
Expand Down
2 changes: 2 additions & 0 deletions bot/modules/dispute/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
const messages = require('./messages');
const globalMessages = require('../../messages');
const { logger } = require('../../../logger');
const OrderEvents = require('../../modules/events/orders');

const dispute = async ctx => {
try {
Expand Down Expand Up @@ -35,6 +36,7 @@ const dispute = async ctx => {
order[`${initiator}_dispute`] = true;
order.status = 'DISPUTE';
await order.save();
OrderEvents.orderUpdated(order);

// If this is a non community order, we may ban the user globally
if (order.community_id) {
Expand Down
8 changes: 4 additions & 4 deletions bot/modules/events/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Events = require('./index');

const TYPES = (exports.TYPES = {
ORDER_CREATED: 'ORDER_CREATED',
ORDER_TAKEN: 'ORDER_TAKEN',
ORDER_UPDATED: 'ORDER_UPDATED',
});

exports.orderCreated = order => {
Expand All @@ -14,10 +14,10 @@ exports.orderCreated = order => {
};
exports.onOrderCreated = fn => Events.subscribe(TYPES.ORDER_CREATED, fn);

exports.orderTaken = order => {
exports.orderUpdated = order => {
Events.dispatch({
type: TYPES.ORDER_TAKEN,
type: TYPES.ORDER_UPDATED,
payload: order,
});
};
exports.onOrderTaken = fn => Events.subscribe(TYPES.ORDER_TAKEN, fn);
exports.onOrderUpdated = fn => Events.subscribe(TYPES.ORDER_UPDATED, fn);
9 changes: 5 additions & 4 deletions bot/modules/nostr/config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const Nostr = require('nostr-tools');
const { getPublicKey, generateSecretKey } = require('nostr-tools/pure');
const { SimplePool } = require('nostr-tools/pool');

const sk = process.env.NOSTR_SK || Nostr.generatePrivateKey();
const pk = Nostr.getPublicKey(sk);
const sk = process.env.NOSTR_SK || generateSecretKey();
const pk = getPublicKey(sk);

exports.getPrivateKey = () => sk;
exports.getPublicKey = () => pk;

const pool = (exports.pool = new Nostr.SimplePool());
const pool = (exports.pool = new SimplePool());
const relays = (env => {
if (!env.RELAYS) return [];
return env.RELAYS.split(',');
Expand Down
94 changes: 48 additions & 46 deletions bot/modules/nostr/events.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,60 @@
const Nostr = require('nostr-tools');
const { finalizeEvent, verifyEvent } = require('nostr-tools/pure');
const Config = require('./config');

const { Community } = require('../../../models');
const { toKebabCase } = require('../../../util');

const KIND = {
ORDER_CREATED: 20100,
};
/// All events broadcasted are Parameterized Replaceable Events,
/// the event kind must be between 30000 and 39999
const kind = 38383;

exports.orderCreated = async order => {
const sk = Config.getPrivateKey();
const pubkey = Config.getPublicKey();

const event = Nostr.getBlankEvent();
event.kind = KIND.ORDER_CREATED;
event.pubkey = pubkey;
event.created_at = Math.floor(Date.now() / 1000);

event.tags.push(['ev', 'order_created']);
event.tags.push(['ot', order.type]);
event.tags.push(['of', order.fiat_code]);

const evData = (order => {
const {
id,
type,
amount,
max_amount,
min_amount,
fiat_code,
fiat_amount,
price_margin,
} = order;
return {
id,
type,
amount,
max_amount,
min_amount,
fiat_code,
fiat_amount,
price_margin,
};
})(order);
event.content = JSON.stringify(evData);
const orderToTags = async order => {
const expiration =
Math.floor(Date.now() / 1000) +
parseInt(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW);
const tags = [];
tags.push(['d', order.id]);
tags.push(['k', order.type]);
tags.push(['f', order.fiat_code]);
tags.push(['s', toKebabCase(order.status)]);
tags.push(['amt', order.amount.toString()]);
tags.push(['fa', order.fiat_amount.toString()]);
tags.push(['pm', order.payment_method]);
tags.push(['premium', order.price_margin.toString()]);
tags.push(['y', 'lnp2pbot']);
tags.push(['z', 'order']);
tags.push(['expiration', expiration.toString()]);
if (order.community_id) {
const community = await Community.findById(order.community_id);
if (community.public) {
if (community.nostr_public_key) {
event.tags.push(['p', community.nostr_public_key]);
}
event.tags.push(['com', order.community_id]);
tags.push(['community_id', order.community_id]);
}
}
event.id = Nostr.getEventHash(event);
event.sig = Nostr.signEvent(event, sk);

return tags;
};

exports.createOrderEvent = async order => {
const myPrivKey = Config.getPrivateKey();

const created_at = Math.floor(Date.now() / 1000);
const tags = await orderToTags(order);

const event = finalizeEvent(
{
kind,
created_at,
tags,
content: '',
},
myPrivKey
);

const ok = verifyEvent(event);
if (!ok) {
console.log('Event not verified');
return;
}

return event;
};
25 changes: 8 additions & 17 deletions bot/modules/nostr/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @ts-check
require('websocket-polyfill');
const { logger } = require('../../../logger');
const Config = require('./config');
const { orderCreated } = require('./events');
const { createOrderEvent } = require('./events');
const Commands = require('./commands');

exports.configure = bot => {
Expand All @@ -20,25 +19,17 @@ exports.configure = bot => {
});

const OrderEvents = require('../events/orders');
OrderEvents.onOrderCreated(async order => {

OrderEvents.onOrderUpdated(async order => {
try {
const event = await orderCreated(order);
await publish(event);
const event = await createOrderEvent(order);
if (event) {
await Promise.any(Config.pool.publish(Config.getRelays(), event));
}

return event;
} catch (err) {
logger.error(err);
}
});
OrderEvents.onOrderTaken(order => {
// todo: notify creator
});
};

async function publish(event) {
const p = new Promise((resolve, reject) => {
const pub = Config.pool.publish(Config.getRelays(), event);
pub.on('ok', () => resolve(event));
pub.on('failed', reject);
});
return p;
}
3 changes: 3 additions & 0 deletions bot/modules/orders/takeOrder.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
validateObjectId,
validateTakeBuyOrder,
} = require('../../validations');
const OrderEvents = require('../../modules/events/orders');

exports.takeOrderActionValidation = async (ctx, next) => {
try {
Expand Down Expand Up @@ -58,6 +59,7 @@ exports.takebuy = async (ctx, bot, orderId) => {
order.seller_id = user._id;
order.taken_at = Date.now();
await order.save();
OrderEvents.orderUpdated(order);
// We delete the messages related to that order from the channel
await deleteOrderFromChannel(order, bot.telegram);
await messages.beginTakeBuyMessage(ctx, bot, user, order);
Expand All @@ -80,6 +82,7 @@ exports.takesell = async (ctx, bot, orderId) => {
order.taken_at = Date.now();

await order.save();
OrderEvents.orderUpdated(order);
// We delete the messages related to that order from the channel
await deleteOrderFromChannel(order, bot.telegram);
await messages.beginTakeSellMessage(ctx, bot, user, order);
Expand Down
2 changes: 1 addition & 1 deletion bot/ordersActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const createOrder = async (
}
await order.save();

OrderEvents.orderCreated(order);
OrderEvents.orderUpdated(order);

return order;
} catch (error) {
Expand Down
3 changes: 3 additions & 0 deletions bot/scenes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const messages = require('./messages');
const { isPendingPayment } = require('../ln');
const { logger } = require('../logger');
const { resolvLightningAddress } = require('../lnurl/lnurl-pay');
const OrderEvents = require('./modules/events/orders');

const addInvoiceWizard = new Scenes.WizardScene(
'ADD_INVOICE_WIZARD_SCENE_ID',
Expand All @@ -25,6 +26,8 @@ const addInvoiceWizard = new Scenes.WizardScene(

order.status = 'WAITING_BUYER_INVOICE';
await order.save();
OrderEvents.orderUpdated(order);

return ctx.wizard.next();
} catch (error) {
logger.error(error);
Expand Down
5 changes: 5 additions & 0 deletions bot/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { I18n, I18nContext } from '@grammyjs/i18n';
import { Message } from 'typegram'
import { UserDocument } from '../models/user'
import { FilterQuery } from 'mongoose';
const OrderEvents = require('./modules/events/orders');

const { limit } = require('@grammyjs/ratelimiter');
const schedule = require('node-schedule');
Expand Down Expand Up @@ -302,6 +303,7 @@ const initialize = (botToken: string, options: Partial<Telegraf.Options<MainCont
order.status = 'FROZEN';
order.action_by = ctx.admin._id;
await order.save();
OrderEvents.orderUpdated(order);

if (order.secret) await settleHoldInvoice({ secret: order.secret });

Expand Down Expand Up @@ -364,6 +366,7 @@ const initialize = (botToken: string, options: Partial<Telegraf.Options<MainCont
const buyer = await User.findOne({ _id: order.buyer_id });
const seller = await User.findOne({ _id: order.seller_id });
await order.save();
OrderEvents.orderUpdated(order);
// we sent a private message to the admin
await messages.successCancelOrderMessage(ctx, ctx.admin, order, ctx.i18n);
// we sent a private message to the seller
Expand Down Expand Up @@ -412,6 +415,7 @@ const initialize = (botToken: string, options: Partial<Telegraf.Options<MainCont
order.status = 'CANCELED';
order.canceled_by = ctx.user.id;
await order.save();
OrderEvents.orderUpdated(order);
// We delete the messages related to that order from the channel
await deleteOrderFromChannel(order, bot.telegram);
}
Expand Down Expand Up @@ -463,6 +467,7 @@ const initialize = (botToken: string, options: Partial<Telegraf.Options<MainCont
const buyer = await User.findOne({ _id: order.buyer_id });
const seller = await User.findOne({ _id: order.seller_id });
await order.save();
OrderEvents.orderUpdated(order);
// we sent a private message to the admin
await messages.successCompleteOrderMessage(ctx, order);
// we sent a private message to the seller
Expand Down
2 changes: 2 additions & 0 deletions jobs/cancel_orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { cancelShowHoldInvoice, cancelAddInvoice } = require('../bot/commands');
const messages = require('../bot/messages');
const { getUserI18nContext, holdInvoiceExpirationInSecs } = require('../util');
const { logger } = require('../logger');
const OrderEvents = require('../bot/modules/events/orders');

const cancelOrders = async bot => {
try {
Expand Down Expand Up @@ -97,6 +98,7 @@ const cancelOrders = async bot => {
for (const order of expiredOrders) {
order.status = 'EXPIRED';
await order.save();
OrderEvents.orderUpdated(order);
logger.info(`Order Id ${order.id} expired!`);
}
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions jobs/pending_payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { logger } = require('../logger');
import { Telegraf } from 'telegraf';
import { I18nContext } from '@grammyjs/i18n';
import { MainContext } from '../bot/start';
const { orderUpdated } = require('../bot/modules/events/orders');

exports.attemptPendingPayments = async (bot: Telegraf<MainContext>): Promise<void> => {
const pendingPayments = await PendingPayment.find({
Expand Down Expand Up @@ -106,6 +107,7 @@ exports.attemptPendingPayments = async (bot: Telegraf<MainContext>): Promise<voi
logger.error(`attemptPendingPayments catch error: ${message}`);
} finally {
await order.save();
orderUpdated(order);
await pending.save();
}
}
Expand Down
Loading

0 comments on commit fe1d187

Please sign in to comment.