Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,10 @@ RELAYS='ws://localhost:7000,ws://localhost:8000,ws://localhost:9000'
# Seconds to wait to allow disputes to be started
DISPUTE_START_WINDOW=600


# Probability of golden honey badger appearance (1 in X orders)
GOLDEN_HONEY_BADGER_PROBABILITY=100

# Number of notification messages sent to the admin, informing them of lack of solvers before disabling the community. The admin receives `MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION - 1` notification messages.
MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION=10

25 changes: 17 additions & 8 deletions bot/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,12 @@ const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null = nu
}
};

const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: IOrder | null = null) => {
const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order?: IOrder | null) => {
try {
ctx.deleteMessage();

if (!order) {
const orderId = (ctx.update as any).callback_query.message.text;
const orderId = (ctx.update as any).callback_query?.message?.text;
if (!orderId) return;
order = await Order.findOne({ _id: orderId });
if (order === null) return;
Expand All @@ -369,7 +370,6 @@ const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: I
const user = await User.findOne({ _id: order.seller_id });
if (!user) return;

// Sellers only can take orders with status WAITING_PAYMENT
if (order.status !== 'WAITING_PAYMENT') {
await messages.invalidDataMessage(ctx, bot, user);
return;
Expand All @@ -384,13 +384,13 @@ const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: I
return;
}

// We create the hold invoice and show it to the seller
const description = ctx.i18n.t('hold_invoice_memo', {
botName: ctx.botInfo.username,
orderId: order._id,
fiatCode: order.fiat_code,
fiatAmount: order.fiat_amount,
});

let amount;
if (order.amount === 0) {
amount = await getBtcFiatPrice(order.fiat_code, order.fiat_amount);
Expand All @@ -399,10 +399,17 @@ const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: I
const marginPercent = order.price_margin / 100;
amount = amount - amount * marginPercent;
amount = Math.floor(amount);
order.fee = await getFee(amount, order.community_id);


order.fee = await getFee(amount, order.community_id, order.is_golden_honey_badger);
order.amount = amount;
}
amount = Math.floor(order.amount + order.fee);


amount = order.is_golden_honey_badger ?
Math.floor(order.amount) :
Math.floor(order.amount + order.fee);

const { request, hash, secret } = await createHoldInvoice({
description,
amount,
Expand All @@ -411,15 +418,17 @@ const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: I
order.secret = secret;
await order.save();

// We monitor the invoice to know when the seller makes the payment
await subscribeInvoice(bot, hash);


await messages.showHoldInvoiceMessage(
ctx,
request,
amount,
order.fiat_code,
order.fiat_amount,
order.random_image
order.random_image,
order.is_golden_honey_badger
);
} catch (error) {
logger.error(`Error in showHoldInvoice: ${error}`);
Expand Down
49 changes: 34 additions & 15 deletions bot/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,16 @@ const pendingSellMessage = async (ctx: MainContext, user: UserDocument, order: I
try {
const orderExpirationWindow =
Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) / 60 / 60;


const pendingSellCaption = i18n.t('pending_sell', {
channel,
orderExpirationWindow: Math.round(orderExpirationWindow),
});

await ctx.telegram.sendMediaGroup(user.tg_id, [{
type: 'photo',
media: { source: Buffer.from(order.random_image, 'base64') },
caption: `${i18n.t('pending_sell', {
channel,
orderExpirationWindow: Math.round(orderExpirationWindow),
})}`,
caption: pendingSellCaption,
}]
);

Expand All @@ -127,6 +129,13 @@ const pendingSellMessage = async (ctx: MainContext, user: UserDocument, order: I
i18n.t('cancel_order_cmd', { orderId: order._id }),
{ parse_mode: 'MarkdownV2' }
);

if (order.is_golden_honey_badger === true && order.type === 'sell') {
await ctx.telegram.sendMessage(
user.tg_id,
i18n.t('golden_honey_badger')
);
}
} catch (error) {
logger.error(error);
}
Expand Down Expand Up @@ -326,11 +335,13 @@ const beginTakeBuyMessage = async (ctx: MainContext, bot: HasTelegram, seller: U
try {
const expirationTime =
Number(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) / 60;

let caption = ctx.i18n.t('begin_take_buy', { expirationTime });

await bot.telegram.sendMediaGroup(seller.tg_id, [{
type: 'photo',
media: { source: Buffer.from(order.random_image, 'base64') },
caption: `${ctx.i18n.t('begin_take_buy', { expirationTime })}`,
caption: caption,
}]
);

Expand All @@ -355,33 +366,41 @@ const beginTakeBuyMessage = async (ctx: MainContext, bot: HasTelegram, seller: U
}
};


const showHoldInvoiceMessage = async (
ctx: MainContext,
request: string,
amount: number,
fiatCode: IOrder["fiat_code"],
fiatAmount: IOrder["fiat_amount"],
randomImage: IOrder["random_image"]
randomImage: string,
isGoldenHoneyBadger: boolean = false
) => {
try {
const currencyObject = getCurrency(fiatCode);
const currency =
!!currencyObject && !!currencyObject.symbol_native
? currencyObject.symbol_native
const currency = getCurrency(fiatCode);
const currencySymbol =
(currency !== null && currency.symbol_native)
? currency.symbol_native
: fiatCode;


await ctx.reply(
ctx.i18n.t('pay_invoice', {
amount: numberFormat(fiatCode, amount),
fiatAmount: numberFormat(fiatCode, fiatAmount!),
currency,
fiatAmount: numberFormat(fiatCode, fiatAmount),
currency: currencySymbol,
})
);


if (isGoldenHoneyBadger) {
await ctx.reply(ctx.i18n.t('golden_honey_badger'));
}

Comment on lines 370 to +399

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improvements to currency symbol handling and Golden Honey Badger messaging.

The function has been enhanced with a more structured approach to currency symbol extraction and includes the Golden Honey Badger messaging when applicable.

Consider using optional chaining for better null safety:

    const currency = getCurrency(fiatCode);
    const currencySymbol = 
-     (currency !== null && currency.symbol_native)
+     currency?.symbol_native
      ? currency.symbol_native
      : fiatCode;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const showHoldInvoiceMessage = async (
ctx: MainContext,
request: string,
amount: number,
fiatCode: IOrder["fiat_code"],
fiatAmount: IOrder["fiat_amount"],
randomImage: IOrder["random_image"]
randomImage: string,
isGoldenHoneyBadger: boolean = false
) => {
try {
const currencyObject = getCurrency(fiatCode);
const currency =
!!currencyObject && !!currencyObject.symbol_native
? currencyObject.symbol_native
const currency = getCurrency(fiatCode);
const currencySymbol =
(currency !== null && currency.symbol_native)
? currency.symbol_native
: fiatCode;
await ctx.reply(
ctx.i18n.t('pay_invoice', {
amount: numberFormat(fiatCode, amount),
fiatAmount: numberFormat(fiatCode, fiatAmount!),
currency,
fiatAmount: numberFormat(fiatCode, fiatAmount),
currency: currencySymbol,
})
);
if (isGoldenHoneyBadger) {
await ctx.reply(ctx.i18n.t('golden_honey_badger'));
}
const currency = getCurrency(fiatCode);
const currencySymbol =
currency?.symbol_native
? currency.symbol_native
: fiatCode;
🧰 Tools
🪛 Biome (1.9.4)

[error] 382-382: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents (early access)
In bot/messages.ts around lines 370 to 399, improve the currency symbol extraction by replacing the explicit null check and conditional with optional chaining to safely access currency.symbol_native. Also, ensure the Golden Honey Badger message is conditionally sent as currently implemented. Update the code to use optional chaining for cleaner and safer access to nested properties.


// Create QR code
const qrBytes = await generateQRWithImage(request, randomImage);

// Send payment request in QR and text

await ctx.replyWithMediaGroup([
{
type: 'photo',
Expand Down
19 changes: 13 additions & 6 deletions bot/modules/orders/takeOrder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Telegraf } from 'telegraf';
import { logger } from '../../../logger';
import { Order } from '../../../models';
import { deleteOrderFromChannel } from '../../../util';
import { deleteOrderFromChannel, generateRandomImage } from '../../../util';
import * as messages from '../../messages';
import { HasTelegram, MainContext } from '../../start';
import { validateUserWaitingOrder, isBannedFromCommunity, validateTakeSellOrder, validateSeller, validateObjectId, validateTakeBuyOrder } from '../../validations';
Expand Down Expand Up @@ -42,26 +42,33 @@ export const takebuy = async (ctx: MainContext, bot: HasTelegram, orderId: strin
if (!(await validateObjectId(ctx, orderId))) return;
const order = await Order.findOne({ _id: orderId });
if (!order) return;
// We verify if the user is not banned on this community

if (await isBannedFromCommunity(user, order.community_id))
return await messages.bannedUserErrorMessage(ctx, user);

if (!(await validateTakeBuyOrder(ctx, bot, user, order))) return;
// We change the status to trigger the expiration of this order
// if the user don't do anything

const { randomImage, isGoldenHoneyBadger } = await generateRandomImage(user._id.toString());

order.status = 'WAITING_PAYMENT';
order.seller_id = user._id;
order.taken_at = new Date(Date.now());

order.random_image = randomImage;
order.is_golden_honey_badger = isGoldenHoneyBadger;

await order.save();
order.status = 'in-progress';
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);
} catch (error) {
logger.error(error);
}
};

export const takesell = async (ctx: MainContext, bot: HasTelegram, orderId: string) => {
try {
const { user } = ctx;
Expand All @@ -85,4 +92,4 @@ export const takesell = async (ctx: MainContext, bot: HasTelegram, orderId: stri
} catch (error) {
logger.error(error);
}
};
};
37 changes: 30 additions & 7 deletions bot/ordersActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ interface BuildDescriptionArguments {
fiatAmount: number[];
fiatCode: string;
paymentMethod: string;
priceMargin: any; // ?
priceMargin: any;
priceFromAPI: boolean;
currency: IFiat;
isGoldenHoneyBadger?: boolean;
}

interface FiatAmountData {
Expand Down Expand Up @@ -90,12 +91,30 @@ const createOrder = async (
}

const fiatAmountData = getFiatAmountData(fiatAmount);


let randomImage = '';
let isGoldenHoneyBadger = false;
let isGoldenHoneyBadgerOrder = false;

if (type === 'sell') {

const result = await generateRandomImage(user._id.toString());
randomImage = result.randomImage;
isGoldenHoneyBadger = result.isGoldenHoneyBadger;
isGoldenHoneyBadgerOrder = isGoldenHoneyBadger;
}

const recalculatedFee = isGoldenHoneyBadgerOrder ?
await getFee(amount, community_id, true) :
fee;

const baseOrderData = {
...fiatAmountData,
amount,
fee,
bot_fee: botFee,
fee: recalculatedFee,
bot_fee: isGoldenHoneyBadgerOrder ? 0 : botFee,
is_golden_honey_badger: isGoldenHoneyBadgerOrder,
community_fee: communityFee,
creator_id: user._id,
type,
Expand All @@ -116,14 +135,14 @@ const createOrder = async (
priceMargin,
priceFromAPI,
currency,
isGoldenHoneyBadger
}),
range_parent_id,
community_id,
is_public: isPublic,
};

let order;

if (type === 'sell') {
order = new Order({
seller_id: user._id,
Expand All @@ -136,10 +155,12 @@ const createOrder = async (
});
}
await order.save();


const randomImage = await generateRandomImage(order._id);
order.random_image = randomImage;
await order.save();
if (type === 'sell' && randomImage) {
order.random_image = randomImage;
await order.save();
}

if (order.status !== 'PENDING') {
OrderEvents.orderUpdated(order);
Expand Down Expand Up @@ -175,6 +196,7 @@ const buildDescription = (
priceMargin,
priceFromAPI,
currency,
isGoldenHoneyBadger
} : BuildDescriptionArguments
) => {
try {
Expand Down Expand Up @@ -237,6 +259,7 @@ const buildDescription = (
description += hashtag;
description += tasaText;
description += rateText;


return description;
} catch (error) {
Expand Down
Binary file added images/Honeybadger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions locales/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 Goldener Honigdachs! Es werden keine Gebühren erhoben! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 Golden Honey Badger! No fee will be charged! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 ¡Tejón de Miel Dorado! ¡No se cobrará comisión! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/fa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 گورکن عسل طلایی! هیچ کارمزدی دریافت نخواهد شد! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 Blaireau de Miel Doré ! Aucun frais ne sera facturé ! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ bot_info: |
User info:

${user_info}
golden_honey_badger: 🍯 Tasso del Miele Dorato! Non verrà addebitata alcuna commissione! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
1 change: 1 addition & 0 deletions locales/ko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ bot_info: |
사용자 정보:

${user_info}
golden_honey_badger: 🍯 골든 허니 배저! 수수료가 부과되지 않습니다! 🦡
user_info: |
Volume traded: ${volume_traded}
Total rating: ${total_rating}
Expand Down
Loading