From 8c46a5b756bd9184caf5e4b08fd02054722576da Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Wed, 3 Sep 2025 16:24:21 +0200 Subject: [PATCH 01/26] feat(notifications): add expired orders notifications --- apps/notification-producer/src/main.ts | 16 +- .../ExpiredOrdersNotificationProducer.ts | 148 ++++++++++++++++++ .../getExpiredOrderNotification.ts | 51 ++++++ .../trade/TradeNotificationProducer.ts | 6 +- .../trade/fromTradeToNotification.ts | 42 ++--- .../producers/trade/getTradeNotifications.ts | 4 +- .../src/utils/getNotificationSummary.ts | 38 +++++ libs/repositories/src/index.ts | 4 + .../ExpiredOrdersRepository.ts | 32 ++++ .../ExpiredOrdersRepositoryPostgres.ts | 54 +++++++ .../expiredOrdersUtils.ts | 14 ++ .../OnChainPlacedOrdersRepositoryPostgres.ts | 8 +- libs/repositories/src/utils/bytesUtils.ts | 7 + libs/services/src/factories.ts | 9 +- 14 files changed, 394 insertions(+), 39 deletions(-) create mode 100644 apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts create mode 100644 apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts create mode 100644 apps/notification-producer/src/utils/getNotificationSummary.ts create mode 100644 libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts create mode 100644 libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts create mode 100644 libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts create mode 100644 libs/repositories/src/utils/bytesUtils.ts diff --git a/apps/notification-producer/src/main.ts b/apps/notification-producer/src/main.ts index fc68cbc1..e77ed02e 100644 --- a/apps/notification-producer/src/main.ts +++ b/apps/notification-producer/src/main.ts @@ -6,11 +6,15 @@ import { getErc20Repository, getIndexerStateRepository, getPushNotificationsRepository, - getPushSubscriptionsRepository + getPushSubscriptionsRepository, + getExpiredOrdersRepository } from '@cowprotocol/services'; import { Runnable } from '../types'; import { TradeNotificationProducer } from './producers/trade/TradeNotificationProducer'; +import { + ExpiredOrdersNotificationProducer +} from './producers/expired-orders/ExpiredOrdersNotificationProducer'; import { ALL_SUPPORTED_CHAIN_IDS } from '@cowprotocol/cow-sdk'; import ms from 'ms'; import { CmsNotificationProducer } from './producers/cms/CmsNotificationProducer'; @@ -36,6 +40,7 @@ async function mainLoop() { const pushSubscriptionsRepository = getPushSubscriptionsRepository(); const indexerStateRepository = getIndexerStateRepository(); const onChainPlacedOrdersRepository = getOnChainPlacedOrdersRepository(); + const expiredOrdersRepository = getExpiredOrdersRepository(); const repositories = { pushNotificationsRepository, @@ -57,6 +62,15 @@ async function mainLoop() { chainId, }); }), + + // Expired order producer + ...chainIds.map((chainId) => { + return new ExpiredOrdersNotificationProducer({ + chainId, + ...repositories, + expiredOrdersRepository + }); + }), ]; // Run all producers in the background diff --git a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts new file mode 100644 index 00000000..8975c222 --- /dev/null +++ b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts @@ -0,0 +1,148 @@ +import { SupportedChainId } from '@cowprotocol/cow-sdk'; +import { + Erc20Repository, + ExpiredOrdersRepository, + IndexerStateRepository, + IndexerStateValue, + OnChainPlacedOrdersRepository, + PushNotificationsRepository, + PushSubscriptionsRepository +} from '@cowprotocol/repositories'; + +import { Runnable } from '../../../types'; +import { doForever, logger } from '@cowprotocol/shared'; +import { getExpiredOrderNotification } from './getExpiredOrderNotification'; + +async function wait(time: number) { + return new Promise((res) => setTimeout(res, time)) +} + +const WAIT_TIME = 10_000; +const POLLING_INTERVAL = 14_000; +const PRODUCER_NAME = 'expired_orders_notification_producer'; + +export type ExpiredOrdersNotificationProducerProps = { + chainId: SupportedChainId; + erc20Repository: Erc20Repository; + indexerStateRepository: IndexerStateRepository; + pushSubscriptionsRepository: PushSubscriptionsRepository; + expiredOrdersRepository: ExpiredOrdersRepository; + pushNotificationsRepository: PushNotificationsRepository; + onChainPlacedOrdersRepository: OnChainPlacedOrdersRepository; +}; + +export interface ExpiredOrdersNotificationProducerState extends IndexerStateValue { + lastCheckTimestamp: string; +} + +export class ExpiredOrdersNotificationProducer implements Runnable { + isStopping = false; + prefix: string; + + constructor(private props: ExpiredOrdersNotificationProducerProps) { + this.prefix = '[ExpiredOrdersNotificationProducer:' + this.props.chainId + ']'; + } + + /** + * Main loop: Run the CMS notification producer. This method runs indefinitely, + * fetching notifications and sending them to the queue. + * + * The method should not throw or finish. + */ + async start(): Promise { + await doForever({ + name: 'ExpiredOrdersNotificationProducer:' + this.props.chainId, + callback: async (stop) => { + if (this.isStopping) { + stop(); + return; + } + await this.processExpiredOrders(); + }, + waitTimeMilliseconds: WAIT_TIME, + logger + }); + } + + async stop(): Promise { + this.isStopping = true; + } + + async processExpiredOrders(): Promise { + return this.pollExpiredOrders().then(() => { + return wait(POLLING_INTERVAL); + }).then(() => { + if (this.isStopping) return + + return this.processExpiredOrders(); + }); + } + + async pollExpiredOrders() { + const { + chainId, + erc20Repository, + indexerStateRepository, + pushSubscriptionsRepository, + expiredOrdersRepository, + pushNotificationsRepository + } = this.props; + + const nowTimestamp = Math.ceil(Date.now() / 1000); + + const stateRegistry = + await indexerStateRepository.get( + PRODUCER_NAME, + chainId + ); + + const lastCheckTimestampRaw = stateRegistry?.state.lastCheckTimestamp; + + if (lastCheckTimestampRaw) { + const lastCheckTimestamp = Number(lastCheckTimestampRaw); + + // TODO: support ethFlowAddresses as well + const accounts = + await pushSubscriptionsRepository.getAllSubscribedAccounts(); + + const expiredOrders = await expiredOrdersRepository.fetchExpiredOrdersForAccounts({ + chainId, + accounts, + lastCheckTimestamp, + nowTimestamp + }); + + logger.debug( + `${this.prefix} got ${expiredOrders.length} expired orders of ${accounts.length} accounts, lastCheckTimestamp=${lastCheckTimestamp}` + ); + + const notifications = await Promise.all(expiredOrders.map(order => { + return getExpiredOrderNotification(order, { + chainId, + nowTimestamp, + lastCheckTimestamp, + isEthFlowOrder: false, // TODO: add eth-flow orders support + owner: order.owner, // TODO: add eth-flow orders support + erc20Repository + }); + })); + + // Return early if there are no notifications + if (notifications.length > 0) { + logger.info( + `${this.prefix} Sending ${notifications.length} notifications`, + JSON.stringify(notifications, null, 2) + ); + + // Post notifications to queue + pushNotificationsRepository.send(notifications); + } + } + + await indexerStateRepository.upsert( + PRODUCER_NAME, + { lastCheckTimestamp: nowTimestamp.toString() }, + chainId + ); + } +} diff --git a/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts b/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts new file mode 100644 index 00000000..efb237e7 --- /dev/null +++ b/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts @@ -0,0 +1,51 @@ +import { PushNotification } from '@cowprotocol/notifications'; +import { Erc20Repository, ParsedExpiredOrder } from '@cowprotocol/repositories'; +import { getExplorerUrl } from '@cowprotocol/shared'; +import { type SupportedChainId } from '@cowprotocol/cow-sdk'; +import { getNotificationSummary } from '../../utils/getNotificationSummary'; + +export interface ExpiredOrderNotificationContext { + chainId: SupportedChainId; + nowTimestamp: number; + lastCheckTimestamp: number; + isEthFlowOrder: boolean; + owner: string; + erc20Repository: Erc20Repository; +} + +export async function getExpiredOrderNotification( + expiredOrder: ParsedExpiredOrder, + notificationContext: ExpiredOrderNotificationContext +): Promise { + const { chainId, lastCheckTimestamp, nowTimestamp, isEthFlowOrder, owner, erc20Repository } = notificationContext; + + const summary = await getNotificationSummary({ + chainId, + isEthFlowOrder, + erc20Repository, + sellAmount: expiredOrder.sellAmount, + buyAmount: expiredOrder.buyAmount, + sellTokenAddress: expiredOrder.sellTokenAddress, + buyTokenAddress: expiredOrder.buyTokenAddress + }); + + const title = `🕐 Order ${summary} has expired`; + const message = ` + Expiration time: ${new Date(expiredOrder.validTo * 1000).toISOString()}. + Account: ${owner}. + `.trim(); + + const url = getExplorerUrl(chainId, expiredOrder.uid); + + return { + id: 'OrderExpired-' + expiredOrder.validTo + '-' + lastCheckTimestamp, + account: expiredOrder.owner.toLowerCase(), + title, + message, + url, + context: { + chainId: chainId.toString(), + nowTimestamp: nowTimestamp.toString() + } + }; +} \ No newline at end of file diff --git a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts index ebec038a..52dfb7a2 100644 --- a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts +++ b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts @@ -204,10 +204,10 @@ export class TradeNotificationProducer implements Runnable { `${this.prefix} Sending ${notifications.length} notifications`, JSON.stringify(notifications, null, 2) ); - } - // Post notifications to queue - this.props.pushNotificationsRepository.send(notifications); + // Post notifications to queue + this.props.pushNotificationsRepository.send(notifications); + } // Update state await indexerStateRepository.upsert( diff --git a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts index b3c24088..36b25180 100644 --- a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts +++ b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts @@ -1,14 +1,8 @@ -import { ALL_SUPPORTED_CHAINS_MAP, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; import { PushNotification } from '@cowprotocol/notifications'; import { Erc20Repository } from '@cowprotocol/repositories'; -import { getAddress } from 'viem'; -import { - ChainNames, - formatAmount, - formatTokenName, - getExplorerUrl, - logger, -} from '@cowprotocol/shared'; +import { getExplorerUrl, logger } from '@cowprotocol/shared'; +import { getNotificationSummary } from '../../utils/getNotificationSummary'; export async function fromTradeToNotification(props: { prefix: string; @@ -42,25 +36,17 @@ export async function fromTradeToNotification(props: { logIndex } = props; - const sellToken = isEthFlowOrder - ? ALL_SUPPORTED_CHAINS_MAP[chainId].nativeCurrency - : await erc20Repository.get( - chainId, - getAddress(sellTokenAddress) - ); - - const buyToken = await erc20Repository.get( + const summary = await getNotificationSummary({ chainId, - getAddress(buyTokenAddress) - ); - - const sellAmountFormatted = formatAmount(sellAmount, sellToken?.decimals); - const buyAmountFormatted = formatAmount(buyAmount, buyToken?.decimals); - - const sellTokenName = formatTokenName(sellToken); - const buyTokenName = formatTokenName(buyToken); + isEthFlowOrder, + erc20Repository, + sellTokenAddress, + buyTokenAddress, + sellAmount, + buyAmount + }); - const title = `Trade ${sellAmountFormatted} ${sellTokenName} for ${buyAmountFormatted} ${buyTokenName} in ${ChainNames[chainId]}`; + const title = `Trade ${summary}`; const message = `Account: ${owner}`; const url = orderUid ? getExplorerUrl(chainId, orderUid) : undefined; @@ -75,7 +61,7 @@ export async function fromTradeToNotification(props: { url, context: { transactionHash, - logIndex: logIndex.toString(), - }, + logIndex: logIndex.toString() + } }; } diff --git a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts index 12f5e246..e494eee8 100644 --- a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts +++ b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts @@ -109,7 +109,7 @@ export async function getTradeNotifications( return orderUids.includes(orderUid.toLowerCase()); }) - : owner; + : owner.toLowerCase(); if (orderOwner) { acc.push( @@ -119,7 +119,7 @@ export async function getTradeNotifications( id: 'Trade-' + log.transactionHash + '-' + log.logIndex, chainId, orderUid, - owner: getAddress(orderOwner), + owner: orderOwner, sellTokenAddress, buyTokenAddress, sellAmount, diff --git a/apps/notification-producer/src/utils/getNotificationSummary.ts b/apps/notification-producer/src/utils/getNotificationSummary.ts new file mode 100644 index 00000000..d17feda1 --- /dev/null +++ b/apps/notification-producer/src/utils/getNotificationSummary.ts @@ -0,0 +1,38 @@ +import { ALL_SUPPORTED_CHAINS_MAP, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { getAddress } from 'viem'; +import { ChainNames, formatAmount, formatTokenName } from '@cowprotocol/shared'; +import { Erc20Repository } from '@cowprotocol/repositories'; + +interface OrderInfoForNotificationParams { + chainId: SupportedChainId; + isEthFlowOrder: boolean; + erc20Repository: Erc20Repository; + sellTokenAddress: string; + buyTokenAddress: string; + sellAmount: string | bigint; + buyAmount: string | bigint; +} + +export async function getNotificationSummary(params: OrderInfoForNotificationParams): Promise { + const { chainId, isEthFlowOrder, erc20Repository } = params; + + const sellToken = isEthFlowOrder + ? ALL_SUPPORTED_CHAINS_MAP[chainId].nativeCurrency + : await erc20Repository.get( + chainId, + getAddress(params.sellTokenAddress) + ); + + const buyToken = await erc20Repository.get( + chainId, + getAddress(params.buyTokenAddress) + ); + + const sellAmountFormatted = formatAmount(BigInt(params.sellAmount), sellToken?.decimals); + const buyAmountFormatted = formatAmount(BigInt(params.buyAmount), buyToken?.decimals); + + const sellTokenName = formatTokenName(sellToken); + const buyTokenName = formatTokenName(buyToken); + + return `${sellAmountFormatted} ${sellTokenName} for ${buyAmountFormatted} ${buyTokenName} in ${ChainNames[chainId]}` +} \ No newline at end of file diff --git a/libs/repositories/src/index.ts b/libs/repositories/src/index.ts index 799d415f..14993cf0 100644 --- a/libs/repositories/src/index.ts +++ b/libs/repositories/src/index.ts @@ -57,6 +57,10 @@ export * from './repos/IndexerStateRepository/IndexerStateRepositoryPostgres'; export * from './repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepository'; export * from './repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres'; +// ExpiredOrdersRepository +export * from './repos/ExpiredOrdersRepository/ExpiredOrdersRepository'; +export * from './repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres'; + // Notifications repositories export * from './repos/PushNotificationsRepository/PushNotificationsRepository'; export * from './repos/PushSubscriptionsRepository/PushSubscriptionsRepository'; diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts new file mode 100644 index 00000000..f1668266 --- /dev/null +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts @@ -0,0 +1,32 @@ +import type { SupportedChainId } from '@cowprotocol/cow-sdk'; + +export interface ExpiredOrdersContext { + chainId: SupportedChainId + accounts: string[] + nowTimestamp: number + lastCheckTimestamp: number +} + +export interface ExpiredOrder { + uid: T; + owner: T; + valid_to: number; + sell_token: T; + buy_token: T; + sell_amount: string; + buy_amount: string; +} + +export interface ParsedExpiredOrder { + uid: string + owner: string + validTo: number + sellTokenAddress: string + buyTokenAddress: string + sellAmount: string + buyAmount: string +} + +export interface ExpiredOrdersRepository { + fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise +} diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts new file mode 100644 index 00000000..7371cae6 --- /dev/null +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -0,0 +1,54 @@ +import { Pool, QueryResult } from 'pg'; +import { + ExpiredOrder, + ExpiredOrdersContext, + ExpiredOrdersRepository, + ParsedExpiredOrder +} from './ExpiredOrdersRepository'; +import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; +import { bytesToHexString } from '../../utils/bytesUtils'; +import { parseExpiredOrder } from './expiredOrdersUtils'; + +export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository { + async fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise { + const { chainId, accounts } = context; + + const prodDb = getOrderBookDbPool('prod', chainId); + const barnDb = getOrderBookDbPool('barn', chainId); + + const prodExpiredOrdersResult = await this.fetchExpiredOrdersFromDb(context, prodDb); + const barnExpiredOrdersResult = await this.fetchExpiredOrdersFromDb(context, barnDb); + + const allExpiredOrders = [...(prodExpiredOrdersResult?.rows || []), ...(barnExpiredOrdersResult?.rows || [])]; + + const accountsMap = accounts.reduce>((acc, account) => { + acc[account.toLowerCase()] = 1; + return acc + }, {}) + + return allExpiredOrders.reduce((acc, order) => { + if (accountsMap[bytesToHexString(order.owner).toLowerCase()]) { + acc.push(parseExpiredOrder(order)) + } + + return acc + }, []) + } + + private async fetchExpiredOrdersFromDb(context: ExpiredOrdersContext, db: Pool): Promise | null> { + const { accounts, lastCheckTimestamp, nowTimestamp } = context; + + if (accounts.length === 0) return null; + + const query = ` + SELECT uid, owner, valid_to, sell_token, buy_token, sell_amount, buy_amount + FROM orders + WHERE valid_to > ${lastCheckTimestamp} + AND valid_to <= ${nowTimestamp} + ORDER BY valid_to ASC + LIMIT 1000 + `; + + return db.query(query); + } +} diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts new file mode 100644 index 00000000..323c486e --- /dev/null +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts @@ -0,0 +1,14 @@ +import { ExpiredOrder, ParsedExpiredOrder } from './ExpiredOrdersRepository'; +import { bytesToHexString } from '../../utils/bytesUtils'; + +export function parseExpiredOrder(order: ExpiredOrder): ParsedExpiredOrder { + return { + uid: bytesToHexString(order.uid), + validTo: order.valid_to, + owner: bytesToHexString(order.owner), + sellTokenAddress: bytesToHexString(order.sell_token), + sellAmount: (order.sell_amount), + buyTokenAddress: bytesToHexString(order.buy_token), + buyAmount: (order.buy_amount), + } +} \ No newline at end of file diff --git a/libs/repositories/src/repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres.ts index f1ac7a85..9149b740 100644 --- a/libs/repositories/src/repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres.ts @@ -2,6 +2,7 @@ import { Pool, QueryResult } from 'pg'; import { OnChainPlacedOrdersRepository } from './OnChainPlacedOrdersRepository'; import { SupportedChainId } from '@cowprotocol/cow-sdk'; import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; +import { bytesToHexString, hexStringToBytes } from '../../utils/bytesUtils'; interface OnChainPlacedOrder { sender: Buffer; @@ -29,11 +30,11 @@ export class OnChainPlacedOrdersRepositoryPostgres implements OnChainPlacedOrder } return orders.reduce((acc, row) => { - const owner = '0x' + row.sender.toString('hex').toLowerCase(); + const owner = bytesToHexString(row.sender).toLowerCase(); acc[owner] = acc[owner] || []; - acc[owner].push('0x' + row.uid.toString('hex').toLowerCase()); + acc[owner].push(bytesToHexString(row.uid).toLowerCase()); return acc; }, {}); @@ -48,7 +49,8 @@ export class OnChainPlacedOrdersRepositoryPostgres implements OnChainPlacedOrder WHERE uid = ANY($1) LIMIT 1000 `; - const params = uids.map(hex => Buffer.from(hex.slice(2), 'hex')); + // TODO: do we need batching for uids? What if there are 20000000 uids? + const params = uids.map(hexStringToBytes); return db.query(query, [params]); } diff --git a/libs/repositories/src/utils/bytesUtils.ts b/libs/repositories/src/utils/bytesUtils.ts new file mode 100644 index 00000000..b6c90af2 --- /dev/null +++ b/libs/repositories/src/utils/bytesUtils.ts @@ -0,0 +1,7 @@ +export function bytesToHexString(bytes: Buffer): string { + return '0x' + bytes.toString('hex') +} + +export function hexStringToBytes(hex: string): Buffer { + return Buffer.from(hex.slice(2), 'hex') +} \ No newline at end of file diff --git a/libs/services/src/factories.ts b/libs/services/src/factories.ts index cda1862e..d827512d 100644 --- a/libs/services/src/factories.ts +++ b/libs/services/src/factories.ts @@ -34,12 +34,13 @@ import { createNewPostgresPool, createTelegramBot, getViemClients, - redisClient + redisClient, + ExpiredOrdersRepository, + ExpiredOrdersRepositoryPostgres } from '@cowprotocol/repositories'; import ms from 'ms'; import { Pool } from 'pg'; -import { DataSource } from 'typeorm'; const DEFAULT_CACHE_VALUE_SECONDS = ms('2min') / 1000; // 2min cache time by default for values const DEFAULT_CACHE_NULL_SECONDS = ms('30min') / 1000; // 30min cache time by default for NULL values (when the repository isn't known) @@ -168,6 +169,10 @@ export function getOnChainPlacedOrdersRepository(): OnChainPlacedOrdersRepositor return new OnChainPlacedOrdersRepositoryPostgres(); } +export function getExpiredOrdersRepository(): ExpiredOrdersRepository { + return new ExpiredOrdersRepositoryPostgres(); +} + export function getSimulationRepository(): SimulationRepository { return new SimulationRepositoryTenderly(); } From 4750e8bfc9dfbe28ddd2abeb0a6b660e5a2cf926 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Wed, 3 Sep 2025 16:43:11 +0200 Subject: [PATCH 02/26] fix: consider order as expired only when it is not filled --- .../ExpiredOrdersRepository.ts | 26 +++++++++-------- .../ExpiredOrdersRepositoryPostgres.ts | 29 ++++++++++++++----- .../expiredOrdersUtils.ts | 1 + 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts index f1668266..ee819e1c 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts @@ -1,10 +1,10 @@ import type { SupportedChainId } from '@cowprotocol/cow-sdk'; export interface ExpiredOrdersContext { - chainId: SupportedChainId - accounts: string[] - nowTimestamp: number - lastCheckTimestamp: number + chainId: SupportedChainId; + accounts: string[]; + nowTimestamp: number; + lastCheckTimestamp: number; } export interface ExpiredOrder { @@ -15,18 +15,20 @@ export interface ExpiredOrder { buy_token: T; sell_amount: string; buy_amount: string; + kind: 'sell' | 'buy'; } export interface ParsedExpiredOrder { - uid: string - owner: string - validTo: number - sellTokenAddress: string - buyTokenAddress: string - sellAmount: string - buyAmount: string + uid: string; + owner: string; + validTo: number; + sellTokenAddress: string; + buyTokenAddress: string; + sellAmount: string; + buyAmount: string; + kind: 'sell' | 'buy'; } export interface ExpiredOrdersRepository { - fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise + fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise; } diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 7371cae6..9bd8c939 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -41,13 +41,28 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository if (accounts.length === 0) return null; const query = ` - SELECT uid, owner, valid_to, sell_token, buy_token, sell_amount, buy_amount - FROM orders - WHERE valid_to > ${lastCheckTimestamp} - AND valid_to <= ${nowTimestamp} - ORDER BY valid_to ASC - LIMIT 1000 - `; + WITH trade_sums AS ( + SELECT + order_uid, + COALESCE(SUM(sell_amount), 0) AS filled_sell, + COALESCE(SUM(buy_amount), 0) AS filled_buy + FROM trades + GROUP BY order_uid + ) + SELECT o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount + FROM orders o + LEFT JOIN trade_sums t ON o.uid = t.order_uid + WHERE + o.valid_to > ${lastCheckTimestamp} + AND o.valid_to <= ${nowTimestamp} + AND ( + (o.kind = 'sell' AND (t.filled_sell < o.sell_amount OR t.filled_sell IS NULL)) + OR + (o.kind = 'buy' AND (t.filled_buy < o.buy_amount OR t.filled_buy IS NULL)) + ) + ORDER BY o.valid_to ASC + LIMIT 1000; + ` return db.query(query); } diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts index 323c486e..7a03df47 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/expiredOrdersUtils.ts @@ -4,6 +4,7 @@ import { bytesToHexString } from '../../utils/bytesUtils'; export function parseExpiredOrder(order: ExpiredOrder): ParsedExpiredOrder { return { uid: bytesToHexString(order.uid), + kind: order.kind, validTo: order.valid_to, owner: bytesToHexString(order.owner), sellTokenAddress: bytesToHexString(order.sell_token), From a2a157a20a25cbf79ca8186ad50b2ad49bcd2707 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Wed, 3 Sep 2025 17:43:16 +0200 Subject: [PATCH 03/26] fix: support eth-flow orders for expired order notifications --- .../ExpiredOrdersNotificationProducer.ts | 33 +++++++++++++++---- .../src/utils/commonUtils.ts | 1 + 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 apps/notification-producer/src/utils/commonUtils.ts diff --git a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts index 8975c222..70230dce 100644 --- a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts +++ b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts @@ -1,4 +1,4 @@ -import { SupportedChainId } from '@cowprotocol/cow-sdk'; +import { BARN_ETH_FLOW_ADDRESSES, ETH_FLOW_ADDRESSES, SupportedChainId } from '@cowprotocol/cow-sdk'; import { Erc20Repository, ExpiredOrdersRepository, @@ -12,6 +12,7 @@ import { import { Runnable } from '../../../types'; import { doForever, logger } from '@cowprotocol/shared'; import { getExpiredOrderNotification } from './getExpiredOrderNotification'; +import { isTruthy } from '../../utils/commonUtils'; async function wait(time: number) { return new Promise((res) => setTimeout(res, time)) @@ -85,7 +86,8 @@ export class ExpiredOrdersNotificationProducer implements Runnable { indexerStateRepository, pushSubscriptionsRepository, expiredOrdersRepository, - pushNotificationsRepository + pushNotificationsRepository, + onChainPlacedOrdersRepository } = this.props; const nowTimestamp = Math.ceil(Date.now() / 1000); @@ -101,28 +103,45 @@ export class ExpiredOrdersNotificationProducer implements Runnable { if (lastCheckTimestampRaw) { const lastCheckTimestamp = Number(lastCheckTimestampRaw); - // TODO: support ethFlowAddresses as well + const ethFlowAddresses = [ETH_FLOW_ADDRESSES[chainId], BARN_ETH_FLOW_ADDRESSES[chainId]].map(t => t.toLowerCase()); + const accounts = await pushSubscriptionsRepository.getAllSubscribedAccounts(); const expiredOrders = await expiredOrdersRepository.fetchExpiredOrdersForAccounts({ chainId, - accounts, + accounts: [...accounts, ...ethFlowAddresses], lastCheckTimestamp, nowTimestamp }); + const ethFlowOrderOwners = expiredOrders.length + ? await onChainPlacedOrdersRepository.getAccountsForOrders(chainId, expiredOrders.map(o => o.uid)) + : {}; + logger.debug( `${this.prefix} got ${expiredOrders.length} expired orders of ${accounts.length} accounts, lastCheckTimestamp=${lastCheckTimestamp}` ); const notifications = await Promise.all(expiredOrders.map(order => { + const isEthFlowOrder = ethFlowAddresses.includes(order.owner.toLowerCase()); + + const orderOwner = isEthFlowOrder + ? Object.keys(ethFlowOrderOwners).find(key => { + const orderUids = ethFlowOrderOwners[key]; + + return orderUids.includes(order.uid.toLowerCase()); + }) + : order.owner.toLowerCase(); + + if (!orderOwner) return Promise.resolve(undefined); + return getExpiredOrderNotification(order, { chainId, nowTimestamp, lastCheckTimestamp, - isEthFlowOrder: false, // TODO: add eth-flow orders support - owner: order.owner, // TODO: add eth-flow orders support + isEthFlowOrder, + owner: orderOwner, erc20Repository }); })); @@ -135,7 +154,7 @@ export class ExpiredOrdersNotificationProducer implements Runnable { ); // Post notifications to queue - pushNotificationsRepository.send(notifications); + pushNotificationsRepository.send(notifications.filter(isTruthy)); } } diff --git a/apps/notification-producer/src/utils/commonUtils.ts b/apps/notification-producer/src/utils/commonUtils.ts new file mode 100644 index 00000000..a2a59f04 --- /dev/null +++ b/apps/notification-producer/src/utils/commonUtils.ts @@ -0,0 +1 @@ +export const isTruthy = (value: T | null | undefined | false): value is T => !!value From 7258aa73812b8e77cfb6fe7b1f474fbe5f9ab049 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Wed, 3 Sep 2025 17:44:04 +0200 Subject: [PATCH 04/26] chore: update POLLING_INTERVAL --- .../expired-orders/ExpiredOrdersNotificationProducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts index 70230dce..cdc4d0b1 100644 --- a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts +++ b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts @@ -19,7 +19,7 @@ async function wait(time: number) { } const WAIT_TIME = 10_000; -const POLLING_INTERVAL = 14_000; +const POLLING_INTERVAL = 120_000; // 2 minutes const PRODUCER_NAME = 'expired_orders_notification_producer'; export type ExpiredOrdersNotificationProducerProps = { From e58829184f5f13bd4bafd9aa05b60fde661cd859 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 08:30:52 +0200 Subject: [PATCH 05/26] chore: update sdk --- package.json | 2 +- yarn.lock | 686 ++++++++++++++++++--------------------------------- 2 files changed, 246 insertions(+), 442 deletions(-) diff --git a/package.json b/package.json index ad70775b..6b143234 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "private": true, "dependencies": { "@cowprotocol/cms": "^0.3.0-RC.4", - "@cowprotocol/cow-sdk": "6.2.0-RC.0", + "@cowprotocol/cow-sdk": "7.0.1-beta.0", "@fastify/autoload": "~5.7.1", "@fastify/caching": "^8.3.0", "@fastify/cors": "^8.2.1", diff --git a/yarn.lock b/yarn.lock index 2d29c32d..d93be2b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1094,46 +1094,151 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cowprotocol/app-data@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@cowprotocol/app-data/-/app-data-3.3.0.tgz#644e7473a00eb5e6694a34d34b4359c6d7ef1060" - integrity sha512-3lfknouwg+j/xSyL5u5FI8rAlGPkzi+QsmXQvPS0O0ETaikx9v9pt5t3pZOv6jtsAmafFBFPtAUp/cdPrDSUaA== +"@cowprotocol/cms@^0.3.0-RC.4": + version "0.3.0-RC.4" + resolved "https://registry.yarnpkg.com/@cowprotocol/cms/-/cms-0.3.0-RC.4.tgz#cfe28820f88e555891c8fc555814beaf6ba14d46" + integrity sha512-6E1D36xaC8Cpecuu1atDGFr8XmNQwcSef8xv5q9/uS11lW150HjiEYeGBr7IObeky5OS2st5zpY0Ll8FOgReZA== dependencies: + openapi-fetch "^0.9.3" + +"@cowprotocol/cow-sdk@7.0.1-beta.0": + version "7.0.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/cow-sdk/-/cow-sdk-7.0.1-beta.0.tgz#1383a585a464949ba9211ac927573f8170d8cdd3" + integrity sha512-sizdFPnoIMWfRWp/6eFcZSsaw/H5MMrpe2JCKdLY9pSR4xCbbVYeUPLmDZqcmHF0O0Gpopk8zPy0Vv6kLooRGA== + dependencies: + "@cowprotocol/sdk-app-data" "workspace:*" + "@cowprotocol/sdk-bridging" "workspace:*" + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-composable" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + "@cowprotocol/sdk-cow-shed" "workspace:*" + "@cowprotocol/sdk-order-book" "workspace:*" + "@cowprotocol/sdk-order-signing" "workspace:*" + "@cowprotocol/sdk-subgraph" "workspace:*" + "@cowprotocol/sdk-trading" "workspace:*" + "@cowprotocol/sdk-weiroll" "workspace:*" + +"@cowprotocol/sdk-app-data@workspace:*": + version "4.1.0-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-app-data/-/sdk-app-data-4.1.0-beta.0.tgz#ed53a525b75bfa6fdfd7d1777e59004a4a8c75e0" + integrity sha512-GCSuHn6d24vB/HS2zFUSwOTVUpn03xXEdnC61t/m4kVLAQ/4TSEbpubbiYJKxMvJJjNDxfcd60rMpg3qSVx59w== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" ajv "^8.11.0" cross-fetch "^3.1.5" ipfs-only-hash "^4.0.0" json-stringify-deterministic "^1.0.8" multiformats "^9.6.4" -"@cowprotocol/cms@^0.3.0-RC.4": - version "0.3.0-RC.4" - resolved "https://registry.yarnpkg.com/@cowprotocol/cms/-/cms-0.3.0-RC.4.tgz#cfe28820f88e555891c8fc555814beaf6ba14d46" - integrity sha512-6E1D36xaC8Cpecuu1atDGFr8XmNQwcSef8xv5q9/uS11lW150HjiEYeGBr7IObeky5OS2st5zpY0Ll8FOgReZA== +"@cowprotocol/sdk-bridging@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-bridging/-/sdk-bridging-0.2.1-beta.0.tgz#8e9ccc93d29692c2c9ce7983836dc44c37231fe3" + integrity sha512-5WSmFqqVtzCt7kKzoZxYhI8nczfTQ3BN+Yc9IGEow1mQCetfe6Elws0pDveLo9XH7DKpMc9qiE4Iuxsh2YovLw== + dependencies: + "@cowprotocol/sdk-app-data" "workspace:*" + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + "@cowprotocol/sdk-cow-shed" "workspace:*" + "@cowprotocol/sdk-order-book" "workspace:*" + "@cowprotocol/sdk-trading" "workspace:*" + "@cowprotocol/sdk-weiroll" "workspace:*" + +"@cowprotocol/sdk-common@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-common/-/sdk-common-0.2.1-beta.0.tgz#620d7f3696cedf4d150dc6c1df09497b1bc9124c" + integrity sha512-CxdATzImnAQDDixZRhPijf7hYynWSFl9CXMefMWBHEI4opstiV+rdhG4FNu/EJoxtdR0otjuIDGTfPPzcRI/zQ== + +"@cowprotocol/sdk-composable@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-composable/-/sdk-composable-0.2.1-beta.0.tgz#eb6dbc5b2644c5ee975cf6af1dbe40ea762e06f5" + integrity sha512-Lvy/PtqxztNOb1cDYYx99gi3X9ZODLXqHAcymNGq5H11xiAIZ4d3Wjqae/5agsSzT2qQRAgGs7MYABFUA4mGVw== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + "@cowprotocol/sdk-order-book" "workspace:*" + "@cowprotocol/sdk-order-signing" "workspace:*" + "@openzeppelin/merkle-tree" "^1.0.8" + +"@cowprotocol/sdk-config@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-config/-/sdk-config-0.2.1-beta.0.tgz#2081224a2d35c3927eb6bcd1fdfb3665bd902b10" + integrity sha512-285FtZljHBBSib16xsfCVcXbekLSsgmVnnlBfHEgGlGWX/Fhftn5/PvePxRwkU/oAKKxFNZXRQjMR7n3anNDrA== dependencies: - openapi-fetch "^0.9.3" + exponential-backoff "^3.1.1" + limiter "^2.1.0" -"@cowprotocol/contracts@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@cowprotocol/contracts/-/contracts-1.8.0.tgz#daffbd9846231c11a74b15a186bb754627e420b0" - integrity sha512-rMEHo1UBB6k4kRoWejHZNGggg6IBVt7vAd8x0FhEvjxhbq3zlAex61f9HpAcDExJNuvfwwDjsOc/7UGztCzhSw== +"@cowprotocol/sdk-contracts-ts@workspace:*": + version "2.1.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-contracts-ts/-/sdk-contracts-ts-2.1.1-beta.0.tgz#2c1364e4e366fa1af279e3cb2e1059db5bd27b73" + integrity sha512-eQcQgNESkzaGpHYXqxlGbYU9hp5d+bR5Ah3I5Z9YHxNp6hu9rZNB33F/YDQYax6IRoQk4Pcv6KU8pOwSehs9EQ== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" -"@cowprotocol/cow-sdk@6.2.0-RC.0": - version "6.2.0-RC.0" - resolved "https://registry.yarnpkg.com/@cowprotocol/cow-sdk/-/cow-sdk-6.2.0-RC.0.tgz#120af7c184ba8ce7b1cfc92e5ebdf83f84237e83" - integrity sha512-Dmg8BYehkeW3QjeovBvW56C6tS7z6S+WOU/W4NUt67gedjrSi5Rd8AxVdZzZ3wFR07ClAYyeQ88UujPbxC+buQ== +"@cowprotocol/sdk-cow-shed@workspace:*": + version "0.2.0-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-cow-shed/-/sdk-cow-shed-0.2.0-beta.0.tgz#a8f92dd884fa93111211e135b2c00c3d4cfffb90" + integrity sha512-vTRmJhsvoLL1/QbegVckzHwFM4Yv4yHji7OHl/tccqQ+Dv+kJGeSvYq/V5MwnZcgyHd9EXXSDOc2EfZ/LcMunA== dependencies: - "@cowprotocol/app-data" "^3.3.0" - "@cowprotocol/contracts" "^1.8.0" - "@ethersproject/abstract-signer" "^5.8.0" - "@openzeppelin/merkle-tree" "^1.0.8" - "@weiroll/weiroll.js" "^0.3.0" + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + +"@cowprotocol/sdk-order-book@workspace:*": + version "0.2.0-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-order-book/-/sdk-order-book-0.2.0-beta.0.tgz#d52f9fc03b7ba5af54eee480a76b5eca0eddbe56" + integrity sha512-lQTkY4KKGqO1SAD8RiJ8SI71+V713b8MwXDws6vNA5weuWsNCORkFQ+ZPz4HDkqH2dp6x9fOnEIPTPSkRENl7g== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" cross-fetch "^3.2.0" - deepmerge "^4.3.1" exponential-backoff "^3.1.2" - graphql "^16.11.0" - graphql-request "^6.1.0" limiter "^3.0.0" +"@cowprotocol/sdk-order-signing@workspace:*": + version "0.2.0-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-order-signing/-/sdk-order-signing-0.2.0-beta.0.tgz#43fc60e869aa8b1caa0b083aaa8e52fea8b21348" + integrity sha512-bB+yG32bF6Izn80ySFEmFv3FaFlDGoC+vnEVBBe7ABy0vN3u3wVBtCV6xz7bUvxPRtwsYrg0WHemTiMYtVEd0w== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + "@cowprotocol/sdk-order-book" "workspace:*" + +"@cowprotocol/sdk-subgraph@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-subgraph/-/sdk-subgraph-0.2.1-beta.0.tgz#c6b87984435a186a12c7a7ce3596faea4eb0ecaf" + integrity sha512-cJgnlkOSCMzO2LXT19+HsqEDxB9IXsc242of4EFxvUaHpCs2io04GmyTMrXhYqf8Qx7kw3DRWZMov7R2q632gA== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + graphql "^16.11.0" + graphql-request "^4.3.0" + +"@cowprotocol/sdk-trading@workspace:*": + version "0.2.1-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-trading/-/sdk-trading-0.2.1-beta.0.tgz#e6b48aac803687f839ebeb3791059c423c4bc983" + integrity sha512-bNLkhRRQDAlfYtP6z+1q6kJ+b1jOI/7e2dnhrzbW+6c0GYbBWYEFXz7Ry9W2y3RRdehx5Ia0CvsydN8jSjMGag== + dependencies: + "@cowprotocol/sdk-app-data" "workspace:*" + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cowprotocol/sdk-contracts-ts" "workspace:*" + "@cowprotocol/sdk-order-book" "workspace:*" + "@cowprotocol/sdk-order-signing" "workspace:*" + deepmerge "^4.3.1" + +"@cowprotocol/sdk-weiroll@workspace:*": + version "0.2.0-beta.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/sdk-weiroll/-/sdk-weiroll-0.2.0-beta.0.tgz#b0730297b8fc8af0599f3cbc863221f195bb0bdb" + integrity sha512-izMKFMylTNH3WRnzoGXJWs4AXxTjWHpWu0kpGuEQuXDx94iuoEaGym1ZddGsjVRl2HGXK9mstnMFIvE1DKHS8A== + dependencies: + "@cowprotocol/sdk-common" "workspace:*" + "@cowprotocol/sdk-config" "workspace:*" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" @@ -1497,21 +1602,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abi@5.8.0", "@ethersproject/abi@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.8.0.tgz#e79bb51940ac35fe6f3262d7fe2cdb25ad5f07d9" - integrity sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q== - dependencies: - "@ethersproject/address" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/hash" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz" @@ -1525,19 +1615,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-provider@5.8.0", "@ethersproject/abstract-provider@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz#7581f9be601afa1d02b95d26b9d9840926a35b0c" - integrity sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg== - dependencies: - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/networks" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - "@ethersproject/web" "^5.8.0" - "@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz" @@ -1549,17 +1626,6 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/abstract-signer@5.8.0", "@ethersproject/abstract-signer@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz#8d7417e95e4094c1797a9762e6789c7356db0754" - integrity sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA== - dependencies: - "@ethersproject/abstract-provider" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/address@5.7.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz" @@ -1571,17 +1637,6 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" -"@ethersproject/address@5.8.0", "@ethersproject/address@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.8.0.tgz#3007a2c352eee566ad745dca1dbbebdb50a6a983" - integrity sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA== - dependencies: - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/rlp" "^5.8.0" - "@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz" @@ -1589,13 +1644,6 @@ dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/base64@5.8.0", "@ethersproject/base64@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.8.0.tgz#61c669c648f6e6aad002c228465d52ac93ee83eb" - integrity sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz" @@ -1604,14 +1652,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/basex@5.8.0", "@ethersproject/basex@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.8.0.tgz#1d279a90c4be84d1c1139114a1f844869e57d03a" - integrity sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz" @@ -1621,15 +1661,6 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bignumber@5.8.0", "@ethersproject/bignumber@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.8.0.tgz#c381d178f9eeb370923d389284efa19f69efa5d7" - integrity sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - bn.js "^5.2.1" - "@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz" @@ -1637,13 +1668,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/bytes@5.8.0", "@ethersproject/bytes@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.8.0.tgz#9074820e1cac7507a34372cadeb035461463be34" - integrity sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A== - dependencies: - "@ethersproject/logger" "^5.8.0" - "@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz" @@ -1651,13 +1675,6 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/constants@5.8.0", "@ethersproject/constants@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.8.0.tgz#12f31c2f4317b113a4c19de94e50933648c90704" - integrity sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg== - dependencies: - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/contracts@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz" @@ -1674,22 +1691,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" -"@ethersproject/contracts@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.8.0.tgz#243a38a2e4aa3e757215ea64e276f8a8c9d8ed73" - integrity sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ== - dependencies: - "@ethersproject/abi" "^5.8.0" - "@ethersproject/abstract-provider" "^5.8.0" - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/address" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - "@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz" @@ -1705,21 +1706,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/hash@5.8.0", "@ethersproject/hash@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.8.0.tgz#b8893d4629b7f8462a90102572f8cd65a0192b4c" - integrity sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA== - dependencies: - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/address" "^5.8.0" - "@ethersproject/base64" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz" @@ -1738,24 +1724,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/hdnode@5.8.0", "@ethersproject/hdnode@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.8.0.tgz#a51ae2a50bcd48ef6fd108c64cbae5e6ff34a761" - integrity sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA== - dependencies: - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/basex" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/pbkdf2" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/sha2" "^5.8.0" - "@ethersproject/signing-key" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - "@ethersproject/wordlists" "^5.8.0" - "@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz" @@ -1775,25 +1743,6 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/json-wallets@5.8.0", "@ethersproject/json-wallets@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz#d18de0a4cf0f185f232eb3c17d5e0744d97eb8c9" - integrity sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w== - dependencies: - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/address" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/hdnode" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/pbkdf2" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/random" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - aes-js "3.0.0" - scrypt-js "3.0.1" - "@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz" @@ -1802,24 +1751,11 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/keccak256@5.8.0", "@ethersproject/keccak256@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.8.0.tgz#d2123a379567faf2d75d2aaea074ffd4df349e6a" - integrity sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng== - dependencies: - "@ethersproject/bytes" "^5.8.0" - js-sha3 "0.8.0" - "@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/logger@5.8.0", "@ethersproject/logger@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.8.0.tgz#f0232968a4f87d29623a0481690a2732662713d6" - integrity sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA== - "@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz" @@ -1827,13 +1763,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/networks@5.8.0", "@ethersproject/networks@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.8.0.tgz#8b4517a3139380cba9fb00b63ffad0a979671fde" - integrity sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg== - dependencies: - "@ethersproject/logger" "^5.8.0" - "@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz" @@ -1842,14 +1771,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" -"@ethersproject/pbkdf2@5.8.0", "@ethersproject/pbkdf2@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz#cd2621130e5dd51f6a0172e63a6e4a0c0a0ec37e" - integrity sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/sha2" "^5.8.0" - "@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz" @@ -1857,13 +1778,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/properties@5.8.0", "@ethersproject/properties@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.8.0.tgz#405a8affb6311a49a91dabd96aeeae24f477020e" - integrity sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw== - dependencies: - "@ethersproject/logger" "^5.8.0" - "@ethersproject/providers@5.7.2": version "5.7.2" resolved "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz" @@ -1890,32 +1804,6 @@ bech32 "1.1.4" ws "7.4.6" -"@ethersproject/providers@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.8.0.tgz#6c2ae354f7f96ee150439f7de06236928bc04cb4" - integrity sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw== - dependencies: - "@ethersproject/abstract-provider" "^5.8.0" - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/address" "^5.8.0" - "@ethersproject/base64" "^5.8.0" - "@ethersproject/basex" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/hash" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/networks" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/random" "^5.8.0" - "@ethersproject/rlp" "^5.8.0" - "@ethersproject/sha2" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - "@ethersproject/web" "^5.8.0" - bech32 "1.1.4" - ws "8.18.0" - "@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz" @@ -1924,14 +1812,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/random@5.8.0", "@ethersproject/random@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.8.0.tgz#1bced04d49449f37c6437c701735a1a022f0057a" - integrity sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz" @@ -1940,14 +1820,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@5.8.0", "@ethersproject/rlp@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.8.0.tgz#5a0d49f61bc53e051532a5179472779141451de5" - integrity sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz" @@ -1957,15 +1829,6 @@ "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" -"@ethersproject/sha2@5.8.0", "@ethersproject/sha2@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.8.0.tgz#8954a613bb78dac9b46829c0a95de561ef74e5e1" - integrity sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - hash.js "1.1.7" - "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz" @@ -1978,18 +1841,6 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/signing-key@5.8.0", "@ethersproject/signing-key@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.8.0.tgz#9797e02c717b68239c6349394ea85febf8893119" - integrity sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - bn.js "^5.2.1" - elliptic "6.6.1" - hash.js "1.1.7" - "@ethersproject/solidity@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz" @@ -2002,18 +1853,6 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/solidity@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.8.0.tgz#429bb9fcf5521307a9448d7358c26b93695379b9" - integrity sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA== - dependencies: - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/sha2" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz" @@ -2023,15 +1862,6 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/strings@5.8.0", "@ethersproject/strings@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.8.0.tgz#ad79fafbf0bd272d9765603215ac74fd7953908f" - integrity sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz" @@ -2047,21 +1877,6 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" -"@ethersproject/transactions@5.8.0", "@ethersproject/transactions@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.8.0.tgz#1e518822403abc99def5a043d1c6f6fe0007e46b" - integrity sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg== - dependencies: - "@ethersproject/address" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/rlp" "^5.8.0" - "@ethersproject/signing-key" "^5.8.0" - "@ethersproject/units@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz" @@ -2071,15 +1886,6 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/units@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.8.0.tgz#c12f34ba7c3a2de0e9fa0ed0ee32f3e46c5c2c6a" - integrity sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ== - dependencies: - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/constants" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/wallet@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz" @@ -2101,27 +1907,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/wallet@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.8.0.tgz#49c300d10872e6986d953e8310dc33d440da8127" - integrity sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA== - dependencies: - "@ethersproject/abstract-provider" "^5.8.0" - "@ethersproject/abstract-signer" "^5.8.0" - "@ethersproject/address" "^5.8.0" - "@ethersproject/bignumber" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/hash" "^5.8.0" - "@ethersproject/hdnode" "^5.8.0" - "@ethersproject/json-wallets" "^5.8.0" - "@ethersproject/keccak256" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/random" "^5.8.0" - "@ethersproject/signing-key" "^5.8.0" - "@ethersproject/transactions" "^5.8.0" - "@ethersproject/wordlists" "^5.8.0" - "@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz" @@ -2133,17 +1918,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/web@5.8.0", "@ethersproject/web@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.8.0.tgz#3e54badc0013b7a801463a7008a87988efce8a37" - integrity sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw== - dependencies: - "@ethersproject/base64" "^5.8.0" - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz" @@ -2155,17 +1929,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/wordlists@5.8.0", "@ethersproject/wordlists@^5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.8.0.tgz#7a5654ee8d1bb1f4dbe43f91d217356d650ad821" - integrity sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg== - dependencies: - "@ethersproject/bytes" "^5.8.0" - "@ethersproject/hash" "^5.8.0" - "@ethersproject/logger" "^5.8.0" - "@ethersproject/properties" "^5.8.0" - "@ethersproject/strings" "^5.8.0" - "@fastify/accept-negotiator@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz" @@ -2323,11 +2086,6 @@ resolved "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.2.tgz" integrity sha512-RMnlf5JxojQt/LqgsQLDyNfQmuO/L+0GwHxsVGRBBSlYC4K45q7e2x1Rh6o8/VgvuwCK+cohQAvbrU0q3ixdbw== -"@graphql-typed-document-node/core@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" - integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz" @@ -3838,13 +3596,6 @@ "@volar/typescript" "~1.8.0" "@vue/language-core" "1.8.4" -"@weiroll/weiroll.js@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@weiroll/weiroll.js/-/weiroll.js-0.3.0.tgz#75a7b6c7016c0c0c2be13e6be408a4ead1c68815" - integrity sha512-RV+iKtY/V/Oc0zoPFPaNGAkOOOV89uGdiocxWd4HT5XNdGob0/XI+07GtP0Dv3B7QLwLTs8QMNKPvsH846svrw== - dependencies: - ethers "^5.3.1" - "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" @@ -4447,6 +4198,14 @@ busboy@^1.6.0: dependencies: streamsearch "^1.1.0" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -5031,6 +4790,15 @@ dotenv@~10.0.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" @@ -5069,19 +4837,6 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" - integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - emittery@^0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" @@ -5191,6 +4946,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" @@ -5203,6 +4963,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz" @@ -5212,6 +4979,16 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz" @@ -5457,42 +5234,6 @@ ethereum-cryptography@^3.0.0: "@scure/bip32" "1.7.0" "@scure/bip39" "1.6.0" -ethers@^5.3.1: - version "5.8.0" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.8.0.tgz#97858dc4d4c74afce83ea7562fe9493cedb4d377" - integrity sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg== - dependencies: - "@ethersproject/abi" "5.8.0" - "@ethersproject/abstract-provider" "5.8.0" - "@ethersproject/abstract-signer" "5.8.0" - "@ethersproject/address" "5.8.0" - "@ethersproject/base64" "5.8.0" - "@ethersproject/basex" "5.8.0" - "@ethersproject/bignumber" "5.8.0" - "@ethersproject/bytes" "5.8.0" - "@ethersproject/constants" "5.8.0" - "@ethersproject/contracts" "5.8.0" - "@ethersproject/hash" "5.8.0" - "@ethersproject/hdnode" "5.8.0" - "@ethersproject/json-wallets" "5.8.0" - "@ethersproject/keccak256" "5.8.0" - "@ethersproject/logger" "5.8.0" - "@ethersproject/networks" "5.8.0" - "@ethersproject/pbkdf2" "5.8.0" - "@ethersproject/properties" "5.8.0" - "@ethersproject/providers" "5.8.0" - "@ethersproject/random" "5.8.0" - "@ethersproject/rlp" "5.8.0" - "@ethersproject/sha2" "5.8.0" - "@ethersproject/signing-key" "5.8.0" - "@ethersproject/solidity" "5.8.0" - "@ethersproject/strings" "5.8.0" - "@ethersproject/transactions" "5.8.0" - "@ethersproject/units" "5.8.0" - "@ethersproject/wallet" "5.8.0" - "@ethersproject/web" "5.8.0" - "@ethersproject/wordlists" "5.8.0" - ethers@^5.7.2: version "5.7.2" resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz" @@ -5581,7 +5322,7 @@ expect@^29.0.0, expect@^29.6.0: jest-message-util "^29.6.0" jest-util "^29.6.0" -exponential-backoff@^3.1.2: +exponential-backoff@^3.1.1, exponential-backoff@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.2.tgz#a8f26adb96bf78e8cd8ad1037928d5e5c0679d91" integrity sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA== @@ -5591,6 +5332,11 @@ extend@~3.0.2: resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" @@ -5898,6 +5644,17 @@ form-data@^2.5.0: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.4.tgz#938273171d3f999286a4557528ce022dc2c98df1" + integrity sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" @@ -6022,11 +5779,35 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -6153,6 +5934,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" @@ -6163,13 +5949,14 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -graphql-request@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" - integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== +graphql-request@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.3.0.tgz#b934e08fcae764aa2cdc697d3c821f046cb5dbf2" + integrity sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow== dependencies: - "@graphql-typed-document-node/core" "^3.2.0" cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" graphql@^16.11.0: version "16.11.0" @@ -6226,6 +6013,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" @@ -7372,6 +7164,11 @@ jsprim@^2.0.2: json-schema "0.4.0" verror "1.10.0" +just-performance@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/just-performance/-/just-performance-4.3.0.tgz#cc2bc8c9227f09e97b6b1df4cd0de2df7ae16db1" + integrity sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q== + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -7409,6 +7206,13 @@ light-my-request@^5.6.1: process-warning "^2.0.0" set-cookie-parser "^2.4.1" +limiter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-2.1.0.tgz#d38d7c5b63729bb84fb0c4d8594b7e955a5182a2" + integrity sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw== + dependencies: + just-performance "4.3.0" + limiter@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/limiter/-/limiter-3.0.0.tgz#03556b76d1a81f547caeecc6b83ecc6f24495715" @@ -7557,6 +7361,11 @@ marked@^15.0.10: resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.10.tgz#466f718a312ef83c7560ae05e4f8092b21e3c48b" integrity sha512-BXzsfFiR2UqXFKRwpugWuCYi9mWd1aX/Yns/X52xWfvfen9lnGEDbJw9ZEjjvLZVqntqT2gX45eYvqb2dIokDw== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" @@ -7622,9 +7431,9 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" @@ -10257,11 +10066,6 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.18.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - ws@8.18.2: version "8.18.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" From 07bcf8717d562cf25f107a1954d0a75f72c43e65 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 09:42:44 +0200 Subject: [PATCH 06/26] feat: add OrdersAppDataRepository to get app-data --- libs/repositories/src/index.ts | 4 + .../OrdersAppDataRepository.ts | 5 + .../OrdersAppDataRepositoryPostgres.ts | 121 ++++++++++++++++++ libs/repositories/src/utils/chunkArray.ts | 9 ++ 4 files changed, 139 insertions(+) create mode 100644 libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts create mode 100644 libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts create mode 100644 libs/repositories/src/utils/chunkArray.ts diff --git a/libs/repositories/src/index.ts b/libs/repositories/src/index.ts index 14993cf0..178ea02c 100644 --- a/libs/repositories/src/index.ts +++ b/libs/repositories/src/index.ts @@ -61,6 +61,10 @@ export * from './repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersReposito export * from './repos/ExpiredOrdersRepository/ExpiredOrdersRepository'; export * from './repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres'; +// OrdersAppDataRepository +export * from './repos/OrdersAppDataRepository/OrdersAppDataRepository'; +export * from './repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres'; + // Notifications repositories export * from './repos/PushNotificationsRepository/PushNotificationsRepository'; export * from './repos/PushSubscriptionsRepository/PushSubscriptionsRepository'; diff --git a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts new file mode 100644 index 00000000..5a62844f --- /dev/null +++ b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts @@ -0,0 +1,5 @@ +import { LatestAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; + +export interface OrdersAppDataRepository { + getAppDataForOrders(chainId: SupportedChainId, uids: string[]): Promise>; +} diff --git a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts new file mode 100644 index 00000000..92ffa799 --- /dev/null +++ b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts @@ -0,0 +1,121 @@ +import { Pool } from 'pg'; +import { OrdersAppDataRepository } from './OrdersAppDataRepository'; +import { LatestAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; +import { bytesToHexString, hexStringToBytes } from '../../utils/bytesUtils'; +import { logger } from '@cowprotocol/shared'; +import { chunkArray } from '../../utils/chunkArray'; + +const LIMIT = 100; + +type UidToAppData = Map; + +interface AppDataFromDbResult { + uidToAppData: UidToAppData; + missingAppDataUids: string[]; +} + +const uidToAppDataCache = new Map(); + +export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository { + async getAppDataForOrders(chainId: SupportedChainId, uids: string[]): Promise { + const cachedResults = uids.reduce((acc: UidToAppData, _uid: string) => { + const uid = _uid.toLowerCase(); + const cached = uidToAppDataCache.get(uid); + + if (cached) acc.set(uid, cached); + + return acc; + }, new Map()); + + if (cachedResults.size === uids.length) return cachedResults; + + const prodDb = getOrderBookDbPool('prod', chainId); + + const uidsToFetch = uids.filter(uid => !cachedResults.has(uid.toLowerCase())); + const prodChunks = chunkArray(uidsToFetch, LIMIT); + + const prodResults = await Promise.all(prodChunks.map(chunk => { + return this.fetchAppDataFromDb(chunk, prodDb); + })); + + const missingAppDataUidsOnProd = prodResults.reduce((acc, result) => { + acc.push(...result.missingAppDataUids); + + return acc; + }, []); + + const prodUidToAppData = prodResults.reduce((acc, result) => { + return this.mergeUidToAppDataMaps(acc, result.uidToAppData); + }, new Map()); + + const totalUidToAppData = this.mergeUidToAppDataMaps(prodUidToAppData, cachedResults); + + if (!missingAppDataUidsOnProd.length) { + this.mergeUidToAppDataMaps(uidToAppDataCache, totalUidToAppData); + + return totalUidToAppData; + } + + const barnDb = getOrderBookDbPool('barn', chainId); + const barnChunks = chunkArray(missingAppDataUidsOnProd, LIMIT); + + const barnResults = await Promise.all(barnChunks.map(chunk => { + return this.fetchAppDataFromDb(chunk, barnDb); + })); + + const barnUidToAppData = barnResults.reduce((acc, result) => { + return this.mergeUidToAppDataMaps(acc, result.uidToAppData); + }, new Map()); + + const results = this.mergeUidToAppDataMaps(totalUidToAppData, barnUidToAppData); + + this.mergeUidToAppDataMaps(uidToAppDataCache, results); + + return results; + } + + private async fetchAppDataFromDb(_uids: string[], db: Pool): Promise { + if (!_uids.length) return { missingAppDataUids: [], uidToAppData: new Map() }; + + const uids = _uids.map((u) => u.toLowerCase()); + const byteaUids = uids.map(hexStringToBytes); + + const query = ` + SELECT o.uid, a.full_app_data + FROM orders o + JOIN app_data a ON o.app_data = a.contract_app_data + WHERE o.uid = ANY ($1) LIMIT ${LIMIT} + `; + + const result = await db.query(query, [byteaUids]); + + const uidToAppData = new Map(); + + for (const row of result.rows) { + const uidHex = bytesToHexString(row.uid).toLowerCase(); + + try { + const fullAppDataHex = JSON.parse(row.full_app_data.toString()); + uidToAppData.set(uidHex, fullAppDataHex); + } catch (error) { + logger.error( + error, + `Could not parse app data from DB` + ); + } + } + + const missingAppDataUids = uids.filter(id => uidToAppData.has(id)); + + return { uidToAppData, missingAppDataUids }; + } + + private mergeUidToAppDataMaps(map1: UidToAppData, map2: UidToAppData): UidToAppData { + for (const [key, value] of map2) { + map1.set(key, value); + } + + return map1; + } +} diff --git a/libs/repositories/src/utils/chunkArray.ts b/libs/repositories/src/utils/chunkArray.ts new file mode 100644 index 00000000..2f1cb9ce --- /dev/null +++ b/libs/repositories/src/utils/chunkArray.ts @@ -0,0 +1,9 @@ +export function chunkArray(arr: T[], size: number): T[][] { + const result: T[][] = []; + + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + + return result; +} \ No newline at end of file From 7125ccc182213a0b49c273f3576effb54ceb6256 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 09:53:34 +0200 Subject: [PATCH 07/26] fix: do not send trade notifications of bridging orders --- apps/notification-producer/src/main.ts | 5 ++- .../trade/TradeNotificationProducer.ts | 6 ++- .../producers/trade/getTradeNotifications.ts | 40 ++++++++++++++++--- .../OrdersAppDataRepositoryPostgres.ts | 15 +++---- libs/services/src/factories.ts | 8 +++- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/apps/notification-producer/src/main.ts b/apps/notification-producer/src/main.ts index e77ed02e..c6a36abd 100644 --- a/apps/notification-producer/src/main.ts +++ b/apps/notification-producer/src/main.ts @@ -7,7 +7,8 @@ import { getIndexerStateRepository, getPushNotificationsRepository, getPushSubscriptionsRepository, - getExpiredOrdersRepository + getExpiredOrdersRepository, + getOrdersAppDataRepository } from '@cowprotocol/services'; import { Runnable } from '../types'; @@ -41,6 +42,7 @@ async function mainLoop() { const indexerStateRepository = getIndexerStateRepository(); const onChainPlacedOrdersRepository = getOnChainPlacedOrdersRepository(); const expiredOrdersRepository = getExpiredOrdersRepository(); + const ordersAppDataRepository = getOrdersAppDataRepository(); const repositories = { pushNotificationsRepository, @@ -59,6 +61,7 @@ async function mainLoop() { ...chainIds.map((chainId) => { return new TradeNotificationProducer({ ...repositories, + ordersAppDataRepository, chainId, }); }), diff --git a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts index 52dfb7a2..1ccadcb6 100644 --- a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts +++ b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts @@ -5,6 +5,7 @@ import { IndexerStateValue, PushNotificationsRepository, OnChainPlacedOrdersRepository, + OrdersAppDataRepository } from '@cowprotocol/repositories'; import { Runnable } from '../../../types'; @@ -27,6 +28,7 @@ export type TradeNotificationProducerProps = { indexerStateRepository: IndexerStateRepository; erc20Repository: Erc20Repository; onChainPlacedOrdersRepository: OnChainPlacedOrdersRepository; + ordersAppDataRepository: OrdersAppDataRepository; }; export interface TradeNotificationProducerState extends IndexerStateValue { @@ -174,7 +176,8 @@ export class TradeNotificationProducer implements Runnable { pushSubscriptionsRepository, indexerStateRepository, erc20Repository, - onChainPlacedOrdersRepository + onChainPlacedOrdersRepository, + ordersAppDataRepository } = this.props; // Get all accounts subscribed to PUSH notifications @@ -189,6 +192,7 @@ export class TradeNotificationProducer implements Runnable { chainId, erc20Repository, onChainPlacedOrdersRepository, + ordersAppDataRepository, prefix: this.prefix, }); diff --git a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts index e494eee8..62e1ab5c 100644 --- a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts +++ b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts @@ -1,11 +1,16 @@ import { + BARN_ETH_FLOW_ADDRESSES, COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS, ETH_FLOW_ADDRESSES, - BARN_ETH_FLOW_ADDRESSES, SupportedChainId } from '@cowprotocol/cow-sdk'; import { PushNotification } from '@cowprotocol/notifications'; -import { Erc20Repository, getViemClients, OnChainPlacedOrdersRepository } from '@cowprotocol/repositories'; +import { + Erc20Repository, + getViemClients, + OnChainPlacedOrdersRepository, + OrdersAppDataRepository +} from '@cowprotocol/repositories'; import { bigIntReplacer, logger } from '@cowprotocol/shared'; import { getAddress, parseAbi } from 'viem'; import { fromTradeToNotification } from './fromTradeToNotification'; @@ -22,13 +27,23 @@ export interface GetTradeNotificationParams { chainId: SupportedChainId; erc20Repository: Erc20Repository; onChainPlacedOrdersRepository: OnChainPlacedOrdersRepository; + ordersAppDataRepository: OrdersAppDataRepository; prefix: string; } export async function getTradeNotifications( params: GetTradeNotificationParams ) { - const { accounts, fromBlock, toBlock, chainId, erc20Repository, onChainPlacedOrdersRepository, prefix } = + const { + accounts, + fromBlock, + toBlock, + chainId, + erc20Repository, + onChainPlacedOrdersRepository, + ordersAppDataRepository, + prefix + } = params; const client = getViemClients()[chainId]; @@ -52,6 +67,16 @@ export async function getTradeNotifications( logger.debug(`${prefix} Found ${logs.length} events`); + const orderUids = logs.reduce((acc, log) => { + const { orderUid } = log.args; + + if (log.eventName !== 'Trade' || !orderUid) return acc; + + acc.push(orderUid.toLowerCase()); + + return acc; + }, []); + const ethFlowOrderIds = logs.reduce((acc, log) => { const { owner, orderUid } = log.args; @@ -68,6 +93,8 @@ export async function getTradeNotifications( ? await onChainPlacedOrdersRepository.getAccountsForOrders(chainId, ethFlowOrderIds) : {}; + const ordersAppData = await ordersAppDataRepository.getAppDataForOrders(chainId, orderUids); + const notificationPromises = logs.reduce[]>( (acc, log) => { switch (log.eventName) { @@ -101,17 +128,20 @@ export async function getTradeNotifications( break; } + const orderUidLower = orderUid.toLowerCase(); const isEthFlowOrder = ethFlowAddresses.includes(owner.toLowerCase()); + const appData = ordersAppData.get(orderUidLower); + const isBridgingOrder = !!appData?.metadata.bridging const orderOwner = isEthFlowOrder ? Object.keys(ethFlowOrderOwners).find(key => { const orderUids = ethFlowOrderOwners[key]; - return orderUids.includes(orderUid.toLowerCase()); + return orderUids.includes(orderUidLower); }) : owner.toLowerCase(); - if (orderOwner) { + if (orderOwner && !isBridgingOrder) { acc.push( fromTradeToNotification({ prefix, diff --git a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts index 92ffa799..8bed0f42 100644 --- a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts +++ b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts @@ -49,11 +49,9 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository return this.mergeUidToAppDataMaps(acc, result.uidToAppData); }, new Map()); - const totalUidToAppData = this.mergeUidToAppDataMaps(prodUidToAppData, cachedResults); + const totalUidToAppData = this.mergeUidToAppDataMaps(cachedResults, prodUidToAppData); if (!missingAppDataUidsOnProd.length) { - this.mergeUidToAppDataMaps(uidToAppDataCache, totalUidToAppData); - return totalUidToAppData; } @@ -75,17 +73,16 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository return results; } - private async fetchAppDataFromDb(_uids: string[], db: Pool): Promise { - if (!_uids.length) return { missingAppDataUids: [], uidToAppData: new Map() }; + private async fetchAppDataFromDb(uids: string[], db: Pool): Promise { + if (!uids.length) return { missingAppDataUids: [], uidToAppData: new Map() }; - const uids = _uids.map((u) => u.toLowerCase()); const byteaUids = uids.map(hexStringToBytes); const query = ` SELECT o.uid, a.full_app_data FROM orders o - JOIN app_data a ON o.app_data = a.contract_app_data - WHERE o.uid = ANY ($1) LIMIT ${LIMIT} + JOIN app_data a ON o.app_data = a.contract_app_data + WHERE o.uid = ANY($1) LIMIT ${LIMIT} `; const result = await db.query(query, [byteaUids]); @@ -106,7 +103,7 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository } } - const missingAppDataUids = uids.filter(id => uidToAppData.has(id)); + const missingAppDataUids = uids.filter(id => !uidToAppData.has(id.toLowerCase())); return { uidToAppData, missingAppDataUids }; } diff --git a/libs/services/src/factories.ts b/libs/services/src/factories.ts index d827512d..3271f704 100644 --- a/libs/services/src/factories.ts +++ b/libs/services/src/factories.ts @@ -36,7 +36,9 @@ import { getViemClients, redisClient, ExpiredOrdersRepository, - ExpiredOrdersRepositoryPostgres + ExpiredOrdersRepositoryPostgres, + OrdersAppDataRepositoryPostgres, + OrdersAppDataRepository } from '@cowprotocol/repositories'; import ms from 'ms'; @@ -173,6 +175,10 @@ export function getExpiredOrdersRepository(): ExpiredOrdersRepository { return new ExpiredOrdersRepositoryPostgres(); } +export function getOrdersAppDataRepository(): OrdersAppDataRepository { + return new OrdersAppDataRepositoryPostgres(); +} + export function getSimulationRepository(): SimulationRepository { return new SimulationRepositoryTenderly(); } From d3f4f2240f738c1b55adab12ab85ba65e1e3c15c Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:18:05 +0200 Subject: [PATCH 08/26] chore: fix comments --- .../expired-orders/ExpiredOrdersNotificationProducer.ts | 2 +- .../src/producers/trade/TradeNotificationProducer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts index cdc4d0b1..653fdfa1 100644 --- a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts +++ b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts @@ -45,7 +45,7 @@ export class ExpiredOrdersNotificationProducer implements Runnable { } /** - * Main loop: Run the CMS notification producer. This method runs indefinitely, + * Main loop: Run the Expired orders notification producer. This method runs indefinitely, * fetching notifications and sending them to the queue. * * The method should not throw or finish. diff --git a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts index 52dfb7a2..720c7065 100644 --- a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts +++ b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts @@ -44,7 +44,7 @@ export class TradeNotificationProducer implements Runnable { } /** - * Main loop: Run the CMS notification producer. This method runs indefinitely, + * Main loop: Run the Trade notification producer. This method runs indefinitely, * fetching notifications and sending them to the queue. * * The method should not throw or finish. From 0390ceae6411997e5fb7f0e132e9fce0d5d1b9ed Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:22:12 +0200 Subject: [PATCH 09/26] chore: improve types --- .../expired-orders/ExpiredOrdersNotificationProducer.ts | 1 - .../ExpiredOrdersRepository/ExpiredOrdersRepository.ts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts index 653fdfa1..a9df6c01 100644 --- a/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts +++ b/apps/notification-producer/src/producers/expired-orders/ExpiredOrdersNotificationProducer.ts @@ -146,7 +146,6 @@ export class ExpiredOrdersNotificationProducer implements Runnable { }); })); - // Return early if there are no notifications if (notifications.length > 0) { logger.info( `${this.prefix} Sending ${notifications.length} notifications`, diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts index ee819e1c..23ff3a0e 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepository.ts @@ -1,4 +1,4 @@ -import type { SupportedChainId } from '@cowprotocol/cow-sdk'; +import type { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk'; export interface ExpiredOrdersContext { chainId: SupportedChainId; @@ -15,7 +15,7 @@ export interface ExpiredOrder { buy_token: T; sell_amount: string; buy_amount: string; - kind: 'sell' | 'buy'; + kind: OrderKind; } export interface ParsedExpiredOrder { @@ -26,7 +26,7 @@ export interface ParsedExpiredOrder { buyTokenAddress: string; sellAmount: string; buyAmount: string; - kind: 'sell' | 'buy'; + kind: OrderKind; } export interface ExpiredOrdersRepository { From d43e6787021dc596245d605de58b5347e42ae1ec Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:23:58 +0200 Subject: [PATCH 10/26] chore: fix using set --- .../ExpiredOrdersRepositoryPostgres.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 9bd8c939..998eb523 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -21,13 +21,13 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository const allExpiredOrders = [...(prodExpiredOrdersResult?.rows || []), ...(barnExpiredOrdersResult?.rows || [])]; - const accountsMap = accounts.reduce>((acc, account) => { - acc[account.toLowerCase()] = 1; + const accountsMap = accounts.reduce>((acc, account) => { + acc.add(account.toLowerCase()); return acc - }, {}) + }, new Set()) return allExpiredOrders.reduce((acc, order) => { - if (accountsMap[bytesToHexString(order.owner).toLowerCase()]) { + if (accountsMap.has(bytesToHexString(order.owner).toLowerCase())) { acc.push(parseExpiredOrder(order)) } From 5cdca68eb64ed21e870ab613b6a274688cda4b11 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:25:47 +0200 Subject: [PATCH 11/26] chore: fix using erc20Repository --- .../src/utils/getNotificationSummary.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/notification-producer/src/utils/getNotificationSummary.ts b/apps/notification-producer/src/utils/getNotificationSummary.ts index d17feda1..dfd9597a 100644 --- a/apps/notification-producer/src/utils/getNotificationSummary.ts +++ b/apps/notification-producer/src/utils/getNotificationSummary.ts @@ -1,4 +1,4 @@ -import { ALL_SUPPORTED_CHAINS_MAP, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { NATIVE_CURRENCY_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk'; import { getAddress } from 'viem'; import { ChainNames, formatAmount, formatTokenName } from '@cowprotocol/shared'; import { Erc20Repository } from '@cowprotocol/repositories'; @@ -16,12 +16,10 @@ interface OrderInfoForNotificationParams { export async function getNotificationSummary(params: OrderInfoForNotificationParams): Promise { const { chainId, isEthFlowOrder, erc20Repository } = params; - const sellToken = isEthFlowOrder - ? ALL_SUPPORTED_CHAINS_MAP[chainId].nativeCurrency - : await erc20Repository.get( - chainId, - getAddress(params.sellTokenAddress) - ); + const sellToken = await erc20Repository.get( + chainId, + isEthFlowOrder ? NATIVE_CURRENCY_ADDRESS : getAddress(params.sellTokenAddress) + ); const buyToken = await erc20Repository.get( chainId, From c570630ecc9cd8e51200f29a617e5c29c8c87f38 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:27:44 +0200 Subject: [PATCH 12/26] chore: fix notification id --- .../src/producers/expired-orders/getExpiredOrderNotification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts b/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts index efb237e7..ee93ab4d 100644 --- a/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts +++ b/apps/notification-producer/src/producers/expired-orders/getExpiredOrderNotification.ts @@ -38,7 +38,7 @@ export async function getExpiredOrderNotification( const url = getExplorerUrl(chainId, expiredOrder.uid); return { - id: 'OrderExpired-' + expiredOrder.validTo + '-' + lastCheckTimestamp, + id: 'OrderExpired-' + expiredOrder.uid + '-' + expiredOrder.validTo + '-' + lastCheckTimestamp, account: expiredOrder.owner.toLowerCase(), title, message, From 70c97bef352d91722be9cdfe84e025c02c92d751 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 11:41:19 +0200 Subject: [PATCH 13/26] chore: add threshold for expired orders --- .../ExpiredOrdersRepositoryPostgres.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 998eb523..324c5b82 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -9,6 +9,9 @@ import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; import { bytesToHexString } from '../../utils/bytesUtils'; import { parseExpiredOrder } from './expiredOrdersUtils'; +const LIMIT = 1000; +const ORDER_EXPIRATION_THRESHOLD = 2; // 2 seconds + export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository { async fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise { const { chainId, accounts } = context; @@ -53,17 +56,21 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository FROM orders o LEFT JOIN trade_sums t ON o.uid = t.order_uid WHERE - o.valid_to > ${lastCheckTimestamp} - AND o.valid_to <= ${nowTimestamp} + o.valid_to > $1 + AND o.valid_to <= $2 AND ( (o.kind = 'sell' AND (t.filled_sell < o.sell_amount OR t.filled_sell IS NULL)) OR (o.kind = 'buy' AND (t.filled_buy < o.buy_amount OR t.filled_buy IS NULL)) ) ORDER BY o.valid_to ASC - LIMIT 1000; + LIMIT $3; ` - return db.query(query); + return db.query(query, [ + lastCheckTimestamp, + nowTimestamp + ORDER_EXPIRATION_THRESHOLD, + LIMIT + ]); } } From 62970391b5e9fd6014db1728d6e3ba434e608bd1 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 12:09:19 +0200 Subject: [PATCH 14/26] fix: optimize expired orders query --- .../ExpiredOrdersRepositoryPostgres.ts | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 324c5b82..c98b8eb1 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -44,27 +44,23 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository if (accounts.length === 0) return null; const query = ` - WITH trade_sums AS ( - SELECT - order_uid, - COALESCE(SUM(sell_amount), 0) AS filled_sell, - COALESCE(SUM(buy_amount), 0) AS filled_buy - FROM trades - GROUP BY order_uid - ) - SELECT o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount - FROM orders o - LEFT JOIN trade_sums t ON o.uid = t.order_uid - WHERE - o.valid_to > $1 - AND o.valid_to <= $2 - AND ( - (o.kind = 'sell' AND (t.filled_sell < o.sell_amount OR t.filled_sell IS NULL)) - OR - (o.kind = 'buy' AND (t.filled_buy < o.buy_amount OR t.filled_buy IS NULL)) + WITH filtered_orders AS ( + SELECT * + FROM orders o + WHERE o.valid_to > $1 + AND o.valid_to <= $2 ) - ORDER BY o.valid_to ASC - LIMIT $3; + SELECT + o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount + FROM filtered_orders o + LEFT JOIN trades t + ON t.order_uid = o.uid + GROUP BY o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount + HAVING ( + (o.kind = 'sell' AND COALESCE(SUM(t.sell_amount), 0) < o.sell_amount) + OR (o.kind = 'buy' AND COALESCE(SUM(t.buy_amount), 0) < o.buy_amount) + ) + LIMIT $3; ` return db.query(query, [ From 6aa9af6a112e34bfa01353aa7d7b17c9b573b089 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 12:10:34 +0200 Subject: [PATCH 15/26] chore: fix ORDER_EXPIRATION_THRESHOLD --- .../ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index c98b8eb1..270346d6 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -10,7 +10,7 @@ import { bytesToHexString } from '../../utils/bytesUtils'; import { parseExpiredOrder } from './expiredOrdersUtils'; const LIMIT = 1000; -const ORDER_EXPIRATION_THRESHOLD = 2; // 2 seconds +const ORDER_EXPIRATION_THRESHOLD = 60; // 1 minute export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository { async fetchExpiredOrdersForAccounts(context: ExpiredOrdersContext): Promise { From f22ce22d188be7a6bd066dc978c57a9d78c1a729 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 13:10:14 +0200 Subject: [PATCH 16/26] chore: adjust expired orders query --- .../ExpiredOrdersRepositoryPostgres.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 270346d6..258c99f3 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -53,8 +53,24 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository SELECT o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount FROM filtered_orders o - LEFT JOIN trades t - ON t.order_uid = o.uid + LEFT JOIN trades t ON t.order_uid = o.uid + WHERE NOT EXISTS ( + SELECT 1 + FROM invalidations i + WHERE i.order_uid = o.uid + ) + AND NOT EXISTS ( + SELECT 1 + FROM onchain_order_invalidations oi + WHERE oi.uid = o.uid + ) + AND NOT EXISTS ( + SELECT 1 + FROM presignature_events pe + WHERE pe.order_uid = o.uid + AND o.signing_scheme = 'presign' + AND pe.signed = false + ) GROUP BY o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount HAVING ( (o.kind = 'sell' AND COALESCE(SUM(t.sell_amount), 0) < o.sell_amount) From 0899701088724eb88b10b59ebbdf4068dcb46993 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 13:12:20 +0200 Subject: [PATCH 17/26] chore: fix sign --- .../ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 258c99f3..2652c0c5 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -81,7 +81,7 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository return db.query(query, [ lastCheckTimestamp, - nowTimestamp + ORDER_EXPIRATION_THRESHOLD, + nowTimestamp - ORDER_EXPIRATION_THRESHOLD, LIMIT ]); } From 89cd92b4b426a9f6efee6fb83c5526a8906e684c Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 13:15:32 +0200 Subject: [PATCH 18/26] chore: add threshold for partially-fillable orders --- .../ExpiredOrdersRepositoryPostgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index 2652c0c5..d3077216 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -73,8 +73,8 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository ) GROUP BY o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount HAVING ( - (o.kind = 'sell' AND COALESCE(SUM(t.sell_amount), 0) < o.sell_amount) - OR (o.kind = 'buy' AND COALESCE(SUM(t.buy_amount), 0) < o.buy_amount) + (o.kind = 'sell' AND COALESCE(SUM(t.sell_amount), 0) < o.sell_amount * 0.999) + OR (o.kind = 'buy' AND COALESCE(SUM(t.buy_amount), 0) < o.buy_amount * 0.999) ) LIMIT $3; ` From 6b3814e2a967422852b2883e72f685fa257a97ab Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 13:22:22 +0200 Subject: [PATCH 19/26] chore: fix LatestAppDataDocVersion --- .../src/producers/trade/getTradeNotifications.ts | 5 +++-- .../OrdersAppDataRepository.ts | 4 ++-- .../OrdersAppDataRepositoryPostgres.ts | 16 ++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts index 62e1ab5c..f7eadc3c 100644 --- a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts +++ b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts @@ -2,7 +2,8 @@ import { BARN_ETH_FLOW_ADDRESSES, COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS, ETH_FLOW_ADDRESSES, - SupportedChainId + SupportedChainId, + LatestAppDataDocVersion } from '@cowprotocol/cow-sdk'; import { PushNotification } from '@cowprotocol/notifications'; import { @@ -131,7 +132,7 @@ export async function getTradeNotifications( const orderUidLower = orderUid.toLowerCase(); const isEthFlowOrder = ethFlowAddresses.includes(owner.toLowerCase()); const appData = ordersAppData.get(orderUidLower); - const isBridgingOrder = !!appData?.metadata.bridging + const isBridgingOrder = !!(appData as LatestAppDataDocVersion)?.metadata?.bridging const orderOwner = isEthFlowOrder ? Object.keys(ethFlowOrderOwners).find(key => { diff --git a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts index 5a62844f..8d79d4ce 100644 --- a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts +++ b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepository.ts @@ -1,5 +1,5 @@ -import { LatestAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { AnyAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; export interface OrdersAppDataRepository { - getAppDataForOrders(chainId: SupportedChainId, uids: string[]): Promise>; + getAppDataForOrders(chainId: SupportedChainId, uids: string[]): Promise>; } diff --git a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts index 8bed0f42..1cfe37cf 100644 --- a/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts +++ b/libs/repositories/src/repos/OrdersAppDataRepository/OrdersAppDataRepositoryPostgres.ts @@ -1,6 +1,6 @@ import { Pool } from 'pg'; import { OrdersAppDataRepository } from './OrdersAppDataRepository'; -import { LatestAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { AnyAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; import { bytesToHexString, hexStringToBytes } from '../../utils/bytesUtils'; import { logger } from '@cowprotocol/shared'; @@ -8,14 +8,14 @@ import { chunkArray } from '../../utils/chunkArray'; const LIMIT = 100; -type UidToAppData = Map; +type UidToAppData = Map; interface AppDataFromDbResult { uidToAppData: UidToAppData; missingAppDataUids: string[]; } -const uidToAppDataCache = new Map(); +const uidToAppDataCache = new Map(); export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository { async getAppDataForOrders(chainId: SupportedChainId, uids: string[]): Promise { @@ -26,7 +26,7 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository if (cached) acc.set(uid, cached); return acc; - }, new Map()); + }, new Map()); if (cachedResults.size === uids.length) return cachedResults; @@ -47,7 +47,7 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository const prodUidToAppData = prodResults.reduce((acc, result) => { return this.mergeUidToAppDataMaps(acc, result.uidToAppData); - }, new Map()); + }, new Map()); const totalUidToAppData = this.mergeUidToAppDataMaps(cachedResults, prodUidToAppData); @@ -64,7 +64,7 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository const barnUidToAppData = barnResults.reduce((acc, result) => { return this.mergeUidToAppDataMaps(acc, result.uidToAppData); - }, new Map()); + }, new Map()); const results = this.mergeUidToAppDataMaps(totalUidToAppData, barnUidToAppData); @@ -82,10 +82,10 @@ export class OrdersAppDataRepositoryPostgres implements OrdersAppDataRepository SELECT o.uid, a.full_app_data FROM orders o JOIN app_data a ON o.app_data = a.contract_app_data - WHERE o.uid = ANY($1) LIMIT ${LIMIT} + WHERE o.uid = ANY($1) LIMIT $2 `; - const result = await db.query(query, [byteaUids]); + const result = await db.query(query, [byteaUids, LIMIT]); const uidToAppData = new Map(); From 11cfaa9bdcef58e49bdadb7c11d73b766a0a36ad Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 13:26:01 +0200 Subject: [PATCH 20/26] chore: npm -> yarn --- apps/notification-producer/Dockerfile | 2 +- apps/telegram/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/notification-producer/Dockerfile b/apps/notification-producer/Dockerfile index 9b237f80..9ed2bfeb 100644 --- a/apps/notification-producer/Dockerfile +++ b/apps/notification-producer/Dockerfile @@ -19,6 +19,6 @@ RUN chown -R notification-producer:notification-producer . # You can remove this install step if you build with `--bundle` option. # The bundled output will include external dependencies. -RUN npm --prefix notification-producer --omit=dev -f install +RUN yarn --cwd notification-producer install --production --force CMD [ "node", "notification-producer" ] diff --git a/apps/telegram/Dockerfile b/apps/telegram/Dockerfile index 3990312b..f8203938 100644 --- a/apps/telegram/Dockerfile +++ b/apps/telegram/Dockerfile @@ -19,6 +19,6 @@ RUN chown -R telegram:telegram . # You can remove this install step if you build with `--bundle` option. # The bundled output will include external dependencies. -RUN npm --prefix telegram --omit=dev -f install +RUN yarn --cwd telegram install --production --force CMD [ "node", "telegram" ] From 8a3bff904758d1da9bcd161960b1df932dc83f45 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Thu, 4 Sep 2025 14:04:51 +0200 Subject: [PATCH 21/26] chore: consider multiple presign events --- .../ExpiredOrdersRepositoryPostgres.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts index d3077216..4bc90ca5 100644 --- a/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/ExpiredOrdersRepository/ExpiredOrdersRepositoryPostgres.ts @@ -66,10 +66,15 @@ export class ExpiredOrdersRepositoryPostgres implements ExpiredOrdersRepository ) AND NOT EXISTS ( SELECT 1 - FROM presignature_events pe - WHERE pe.order_uid = o.uid - AND o.signing_scheme = 'presign' - AND pe.signed = false + FROM ( + SELECT pe.signed + FROM presignature_events pe + WHERE pe.order_uid = o.uid + ORDER BY pe.block_number DESC + LIMIT 1 + ) latest_pe + WHERE o.signing_scheme = 'presign' + AND latest_pe.signed = false ) GROUP BY o.uid, o.kind, o.owner, o.valid_to, o.sell_token, o.buy_token, o.sell_amount, o.buy_amount HAVING ( From ac2e321c11aa3d1bd5ac06b8e4393f2f99f6e4b0 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 5 Sep 2025 14:32:41 +0200 Subject: [PATCH 22/26] fix: warn instead of error when orm analytics env vars are missing --- apps/api/src/app/plugins/orm-analytics.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/src/app/plugins/orm-analytics.ts b/apps/api/src/app/plugins/orm-analytics.ts index b7c9943e..0758936c 100644 --- a/apps/api/src/app/plugins/orm-analytics.ts +++ b/apps/api/src/app/plugins/orm-analytics.ts @@ -1,9 +1,9 @@ -import 'reflect-metadata'; +import { isDbEnabled } from '@cowprotocol/repositories'; import { FastifyInstance } from 'fastify'; -import typeORMPlugin from 'typeorm-fastify-plugin'; import fp from 'fastify-plugin'; +import 'reflect-metadata'; +import typeORMPlugin from 'typeorm-fastify-plugin'; import { PoolInfo } from '../data/poolInfo'; -import { isDbEnabled } from '@cowprotocol/repositories'; export default fp(async function (fastify: FastifyInstance) { if (!isDbEnabled) { @@ -26,7 +26,7 @@ export default fp(async function (fastify: FastifyInstance) { ); if (dbParamsAreInvalid) { - console.error( + console.warn( 'Invalid CoW Analytics database parameters, please check COW_ANALYTICS_* env vars' ); return; From ed5e2640a9c4d13d59c94a276b4b73b9e3403a17 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 5 Sep 2025 14:33:27 +0200 Subject: [PATCH 23/26] feat: add OrdersRepository --- libs/repositories/src/index.ts | 6 +- .../OrdersRepository/OrdersRepository.ts | 8 +++ .../OrdersRepositoryPostgres.ts | 59 +++++++++++++++++++ libs/services/src/factories.ts | 18 ++++-- 4 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 libs/repositories/src/repos/OrdersRepository/OrdersRepository.ts create mode 100644 libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts diff --git a/libs/repositories/src/index.ts b/libs/repositories/src/index.ts index 178ea02c..0d92b2b5 100644 --- a/libs/repositories/src/index.ts +++ b/libs/repositories/src/index.ts @@ -5,13 +5,13 @@ export * from './utils/isDbEnabled'; // Data-sources export * from './datasources/cms'; export * from './datasources/cowApi'; +export * from './datasources/orderBookDbPool'; export * from './datasources/orm/postgresOrm'; export * from './datasources/postgresPlain'; export * from './datasources/rabbitMq'; export * from './datasources/redis'; export * from './datasources/telegram'; export * from './datasources/viem'; -export * from './datasources/orderBookDbPool'; // Data sources export { COINGECKO_PRO_BASE_URL } from './datasources/coingecko'; @@ -53,6 +53,10 @@ export * from './repos/IndexerStateRepository/IndexerStateRepository'; export * from './repos/IndexerStateRepository/IndexerStateRepositoryOrm'; export * from './repos/IndexerStateRepository/IndexerStateRepositoryPostgres'; +// OrdersRepository +export * from './repos/OrdersRepository/OrdersRepository'; +export * from './repos/OrdersRepository/OrdersRepositoryPostgres'; + // OnChainPlacedOrdersRepository export * from './repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepository'; export * from './repos/OnchainPlacedOrdersRepository/OnChainPlacedOrdersRepositoryPostgres'; diff --git a/libs/repositories/src/repos/OrdersRepository/OrdersRepository.ts b/libs/repositories/src/repos/OrdersRepository/OrdersRepository.ts new file mode 100644 index 00000000..2bec5557 --- /dev/null +++ b/libs/repositories/src/repos/OrdersRepository/OrdersRepository.ts @@ -0,0 +1,8 @@ +import { Order, SupportedChainId } from '@cowprotocol/cow-sdk'; + +export interface OrdersRepository { + getOrders( + chainId: SupportedChainId, + uids: string[] + ): Promise>>; +} diff --git a/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts b/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts new file mode 100644 index 00000000..3d2c6954 --- /dev/null +++ b/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts @@ -0,0 +1,59 @@ +import { Order, SupportedChainId } from '@cowprotocol/cow-sdk'; +import { logger } from '@cowprotocol/shared'; +import { Pool } from 'pg'; +import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; +import { bytesToHexString, hexStringToBytes } from '../../utils/bytesUtils'; +import { OrdersRepository } from './OrdersRepository'; + +export class OrdersRepositoryPostgres implements OrdersRepository { + async getOrders( + chainId: SupportedChainId, + uids: string[] + ): Promise>> { + const prodDb = getOrderBookDbPool('prod', chainId); + const prodOrders = await this.fetchOrdersFromDb(uids, prodDb); + const orders: Partial[] = prodOrders || []; + + logger.info(`Prod orders: ${JSON.stringify(prodOrders, null, 2)}`); + + if (prodOrders?.length !== uids.length) { + const barnDb = getOrderBookDbPool('barn', chainId); + const barnOrders = await this.fetchOrdersFromDb(uids, barnDb); + + logger.info(`Barn orders: ${JSON.stringify(barnOrders, null, 2)}`); + + if (barnOrders?.length) { + orders.push(...barnOrders); + } + } + + logger.info(`Orders: ${JSON.stringify(orders, null, 2)}`); + + return orders.reduce>>((acc, order) => { + acc.set(order.uid!, order); + return acc; + }, new Map()); + } + + private async fetchOrdersFromDb( + uids: string[], + db: Pool + ): Promise[] | null> { + if (uids.length === 0) return null; + + const byteaUids = uids.map(hexStringToBytes); + // Note: add more fields as needed + const query = ` + SELECT uid, partially_fillable FROM orders WHERE uid = ANY($1) LIMIT 1000 + `; + + const result = await db.query(query, [byteaUids]); + + return result.rows.map((row) => { + return { + partiallyFillable: row.partially_fillable, + uid: bytesToHexString(row.uid).toLowerCase(), + }; + }); + } +} diff --git a/libs/services/src/factories.ts b/libs/services/src/factories.ts index 3271f704..829faf85 100644 --- a/libs/services/src/factories.ts +++ b/libs/services/src/factories.ts @@ -9,10 +9,16 @@ import { Erc20RepositoryFallback, Erc20RepositoryNative, Erc20RepositoryViem, + ExpiredOrdersRepository, + ExpiredOrdersRepositoryPostgres, IndexerStateRepository, IndexerStateRepositoryPostgres, OnChainPlacedOrdersRepository, OnChainPlacedOrdersRepositoryPostgres, + OrdersAppDataRepository, + OrdersAppDataRepositoryPostgres, + OrdersRepository, + OrdersRepositoryPostgres, PushNotificationsRepository, PushNotificationsRepositoryRabbit, PushSubscriptionsRepository, @@ -35,10 +41,6 @@ import { createTelegramBot, getViemClients, redisClient, - ExpiredOrdersRepository, - ExpiredOrdersRepositoryPostgres, - OrdersAppDataRepositoryPostgres, - OrdersAppDataRepository } from '@cowprotocol/repositories'; import ms from 'ms'; @@ -108,7 +110,7 @@ export function getUsdRepository( ): UsdRepository { return new UsdRepositoryFallback([ getUsdRepositoryCoingecko(cacheRepository), - getUsdRepositoryCow(cacheRepository, erc20Repository) + getUsdRepositoryCow(cacheRepository, erc20Repository), ]); } @@ -141,7 +143,7 @@ export function getTokenHolderRepository( ): TokenHolderRepository { return new TokenHolderRepositoryFallback([ getTokenHolderRepositoryMoralis(cacheRepository), - getTokenHolderRepositoryEthplorer(cacheRepository) + getTokenHolderRepositoryEthplorer(cacheRepository), ]); } @@ -171,6 +173,10 @@ export function getOnChainPlacedOrdersRepository(): OnChainPlacedOrdersRepositor return new OnChainPlacedOrdersRepositoryPostgres(); } +export function getOrdersRepository(): OrdersRepository { + return new OrdersRepositoryPostgres(); +} + export function getExpiredOrdersRepository(): ExpiredOrdersRepository { return new ExpiredOrdersRepositoryPostgres(); } From e3b25b6f395bce2c8bcce9d55519d561f5c22159 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 5 Sep 2025 14:36:16 +0200 Subject: [PATCH 24/26] feat: add order type info to the trade notification msg --- apps/notification-producer/src/main.ts | 23 ++++---- .../trade/TradeNotificationProducer.ts | 18 ++++-- .../trade/fromTradeToNotification.ts | 23 +++++--- .../producers/trade/getTradeNotifications.ts | 57 +++++++++++++------ .../src/utils/getOrderTitle.ts | 29 ++++++++++ 5 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 apps/notification-producer/src/utils/getOrderTitle.ts diff --git a/apps/notification-producer/src/main.ts b/apps/notification-producer/src/main.ts index c6a36abd..9e6467c2 100644 --- a/apps/notification-producer/src/main.ts +++ b/apps/notification-producer/src/main.ts @@ -2,24 +2,23 @@ import 'reflect-metadata'; import { getCacheRepository, - getOnChainPlacedOrdersRepository, getErc20Repository, + getExpiredOrdersRepository, getIndexerStateRepository, + getOnChainPlacedOrdersRepository, + getOrdersAppDataRepository, + getOrdersRepository, getPushNotificationsRepository, getPushSubscriptionsRepository, - getExpiredOrdersRepository, - getOrdersAppDataRepository } from '@cowprotocol/services'; -import { Runnable } from '../types'; -import { TradeNotificationProducer } from './producers/trade/TradeNotificationProducer'; -import { - ExpiredOrdersNotificationProducer -} from './producers/expired-orders/ExpiredOrdersNotificationProducer'; import { ALL_SUPPORTED_CHAIN_IDS } from '@cowprotocol/cow-sdk'; +import { logger } from '@cowprotocol/shared'; import ms from 'ms'; +import { Runnable } from '../types'; import { CmsNotificationProducer } from './producers/cms/CmsNotificationProducer'; -import { logger } from '@cowprotocol/shared'; +import { ExpiredOrdersNotificationProducer } from './producers/expired-orders/ExpiredOrdersNotificationProducer'; +import { TradeNotificationProducer } from './producers/trade/TradeNotificationProducer'; const TIMEOUT_STOP_PRODUCERS = ms(`30s`); @@ -43,6 +42,7 @@ async function mainLoop() { const onChainPlacedOrdersRepository = getOnChainPlacedOrdersRepository(); const expiredOrdersRepository = getExpiredOrdersRepository(); const ordersAppDataRepository = getOrdersAppDataRepository(); + const ordersRepository = getOrdersRepository(); const repositories = { pushNotificationsRepository, @@ -50,6 +50,8 @@ async function mainLoop() { indexerStateRepository, erc20Repository, onChainPlacedOrdersRepository, + ordersAppDataRepository, + ordersRepository, }; // Create all producers @@ -61,7 +63,6 @@ async function mainLoop() { ...chainIds.map((chainId) => { return new TradeNotificationProducer({ ...repositories, - ordersAppDataRepository, chainId, }); }), @@ -71,7 +72,7 @@ async function mainLoop() { return new ExpiredOrdersNotificationProducer({ chainId, ...repositories, - expiredOrdersRepository + expiredOrdersRepository, }); }), ]; diff --git a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts index dd0c3759..43d9eb81 100644 --- a/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts +++ b/apps/notification-producer/src/producers/trade/TradeNotificationProducer.ts @@ -3,15 +3,18 @@ import { Erc20Repository, getViemClients, IndexerStateValue, - PushNotificationsRepository, OnChainPlacedOrdersRepository, - OrdersAppDataRepository + OrdersAppDataRepository, + OrdersRepository, + PushNotificationsRepository, } from '@cowprotocol/repositories'; -import { Runnable } from '../../../types'; -import { PushSubscriptionsRepository } from '@cowprotocol/repositories'; -import { IndexerStateRepository } from '@cowprotocol/repositories'; +import { + IndexerStateRepository, + PushSubscriptionsRepository, +} from '@cowprotocol/repositories'; import { doForever, logger } from '@cowprotocol/shared'; +import { Runnable } from '../../../types'; import { getTradeNotifications } from './getTradeNotifications'; const WAIT_TIME = 10000; @@ -29,6 +32,7 @@ export type TradeNotificationProducerProps = { erc20Repository: Erc20Repository; onChainPlacedOrdersRepository: OnChainPlacedOrdersRepository; ordersAppDataRepository: OrdersAppDataRepository; + ordersRepository: OrdersRepository; }; export interface TradeNotificationProducerState extends IndexerStateValue { @@ -177,7 +181,8 @@ export class TradeNotificationProducer implements Runnable { indexerStateRepository, erc20Repository, onChainPlacedOrdersRepository, - ordersAppDataRepository + ordersAppDataRepository, + ordersRepository, } = this.props; // Get all accounts subscribed to PUSH notifications @@ -193,6 +198,7 @@ export class TradeNotificationProducer implements Runnable { erc20Repository, onChainPlacedOrdersRepository, ordersAppDataRepository, + ordersRepository, prefix: this.prefix, }); diff --git a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts index 36b25180..a5357015 100644 --- a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts +++ b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts @@ -1,8 +1,9 @@ -import { SupportedChainId } from '@cowprotocol/cow-sdk'; +import { AnyAppDataDocVersion, SupportedChainId } from '@cowprotocol/cow-sdk'; import { PushNotification } from '@cowprotocol/notifications'; import { Erc20Repository } from '@cowprotocol/repositories'; import { getExplorerUrl, logger } from '@cowprotocol/shared'; import { getNotificationSummary } from '../../utils/getNotificationSummary'; +import { getOrderTitle } from '../../utils/getOrderTitle'; export async function fromTradeToNotification(props: { prefix: string; @@ -19,6 +20,8 @@ export async function fromTradeToNotification(props: { erc20Repository: Erc20Repository; transactionHash: string; logIndex: number; + isPartiallyFillable: boolean; + appData?: AnyAppDataDocVersion; }): Promise { const { id, @@ -33,7 +36,9 @@ export async function fromTradeToNotification(props: { prefix, orderUid, transactionHash, - logIndex + logIndex, + appData, + isPartiallyFillable, } = props; const summary = await getNotificationSummary({ @@ -43,25 +48,27 @@ export async function fromTradeToNotification(props: { sellTokenAddress, buyTokenAddress, sellAmount, - buyAmount + buyAmount, }); - const title = `Trade ${summary}`; + const title = getOrderTitle(appData, isPartiallyFillable); + + const fullMessage = `${title} ${summary}`; const message = `Account: ${owner}`; const url = orderUid ? getExplorerUrl(chainId, orderUid) : undefined; logger.info( - `${prefix} New ${message} for ${owner}. Tx=${transactionHash}, logIndex=${logIndex}` + `${prefix} New ${message} for ${owner}. Tx=${transactionHash}, logIndex=${logIndex}, ${fullMessage}` ); return { id, account: owner, - title, + title: fullMessage, message, url, context: { transactionHash, - logIndex: logIndex.toString() - } + logIndex: logIndex.toString(), + }, }; } diff --git a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts index f7eadc3c..302f0be1 100644 --- a/apps/notification-producer/src/producers/trade/getTradeNotifications.ts +++ b/apps/notification-producer/src/producers/trade/getTradeNotifications.ts @@ -1,16 +1,18 @@ import { + AnyAppDataDocVersion, BARN_ETH_FLOW_ADDRESSES, COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS, ETH_FLOW_ADDRESSES, + LatestAppDataDocVersion, SupportedChainId, - LatestAppDataDocVersion } from '@cowprotocol/cow-sdk'; import { PushNotification } from '@cowprotocol/notifications'; import { Erc20Repository, getViemClients, OnChainPlacedOrdersRepository, - OrdersAppDataRepository + OrdersAppDataRepository, + OrdersRepository, } from '@cowprotocol/repositories'; import { bigIntReplacer, logger } from '@cowprotocol/shared'; import { getAddress, parseAbi } from 'viem'; @@ -18,7 +20,7 @@ import { fromTradeToNotification } from './fromTradeToNotification'; const EVENTS = parseAbi([ // 'event OrderInvalidated(address indexed owner, bytes orderUid)', // Do not index this event - 'event Trade(address indexed owner, address sellToken, address buyToken, uint256 sellAmount, uint256 buyAmount, uint256 feeAmount, bytes orderUid)' + 'event Trade(address indexed owner, address sellToken, address buyToken, uint256 sellAmount, uint256 buyAmount, uint256 feeAmount, bytes orderUid)', ]); export interface GetTradeNotificationParams { @@ -29,6 +31,7 @@ export interface GetTradeNotificationParams { erc20Repository: Erc20Repository; onChainPlacedOrdersRepository: OnChainPlacedOrdersRepository; ordersAppDataRepository: OrdersAppDataRepository; + ordersRepository: OrdersRepository; prefix: string; } @@ -43,13 +46,16 @@ export async function getTradeNotifications( erc20Repository, onChainPlacedOrdersRepository, ordersAppDataRepository, - prefix - } = - params; + ordersRepository, + prefix, + } = params; const client = getViemClients()[chainId]; - const ethFlowAddresses = [ETH_FLOW_ADDRESSES[chainId], BARN_ETH_FLOW_ADDRESSES[chainId]].map(t => t.toLowerCase()); + const ethFlowAddresses = [ + ETH_FLOW_ADDRESSES[chainId], + BARN_ETH_FLOW_ADDRESSES[chainId], + ].map((t) => t.toLowerCase()); const logs = await client.getLogs({ events: EVENTS, @@ -57,8 +63,8 @@ export async function getTradeNotifications( toBlock, address: getAddress(COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[chainId]), args: { - owner: [...accounts, ...ethFlowAddresses] - } as any + owner: [...accounts, ...ethFlowAddresses], + } as any, }); // Return empty array if no events @@ -91,10 +97,18 @@ export async function getTradeNotifications( }, []); const ethFlowOrderOwners = ethFlowOrderIds.length - ? await onChainPlacedOrdersRepository.getAccountsForOrders(chainId, ethFlowOrderIds) + ? await onChainPlacedOrdersRepository.getAccountsForOrders( + chainId, + ethFlowOrderIds + ) : {}; - const ordersAppData = await ordersAppDataRepository.getAppDataForOrders(chainId, orderUids); + const ordersAppData = await ordersAppDataRepository.getAppDataForOrders( + chainId, + orderUids + ); + + const orders = await ordersRepository.getOrders(chainId, orderUids); const notificationPromises = logs.reduce[]>( (acc, log) => { @@ -107,7 +121,7 @@ export async function getTradeNotifications( buyToken: buyTokenAddress, sellAmount, buyAmount, - feeAmount + feeAmount, } = log.args; if ( @@ -130,16 +144,17 @@ export async function getTradeNotifications( } const orderUidLower = orderUid.toLowerCase(); + const order = orders.get(orderUidLower); const isEthFlowOrder = ethFlowAddresses.includes(owner.toLowerCase()); const appData = ordersAppData.get(orderUidLower); - const isBridgingOrder = !!(appData as LatestAppDataDocVersion)?.metadata?.bridging + const isBridgingOrder = getIsBridgingOrder(appData); const orderOwner = isEthFlowOrder - ? Object.keys(ethFlowOrderOwners).find(key => { - const orderUids = ethFlowOrderOwners[key]; + ? Object.keys(ethFlowOrderOwners).find((key) => { + const orderUids = ethFlowOrderOwners[key]; - return orderUids.includes(orderUidLower); - }) + return orderUids.includes(orderUidLower); + }) : owner.toLowerCase(); if (orderOwner && !isBridgingOrder) { @@ -158,7 +173,9 @@ export async function getTradeNotifications( feeAmount, erc20Repository, transactionHash: log.transactionHash, - logIndex: log.logIndex + logIndex: log.logIndex, + isPartiallyFillable: order?.partiallyFillable ?? false, + appData: appData, }) ); } @@ -181,3 +198,7 @@ export async function getTradeNotifications( return Promise.all(notificationPromises); } + +function getIsBridgingOrder(appData: AnyAppDataDocVersion | undefined) { + return !!(appData as LatestAppDataDocVersion)?.metadata?.bridging; +} diff --git a/apps/notification-producer/src/utils/getOrderTitle.ts b/apps/notification-producer/src/utils/getOrderTitle.ts new file mode 100644 index 00000000..f45c59e0 --- /dev/null +++ b/apps/notification-producer/src/utils/getOrderTitle.ts @@ -0,0 +1,29 @@ +import { AnyAppDataDocVersion } from '@cowprotocol/cow-sdk'; + +export function getOrderTitle( + appData: AnyAppDataDocVersion | undefined, + isPartiallyFillable: boolean +) { + const { metadata } = appData || {}; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const orderClass = (metadata as any)?.orderClass?.orderClass || 'unknown'; + + switch (orderClass) { + case 'market': + return 'Swap order filled'; + case 'limit': + // TODO: for partially fillable orders, we need to check whether the order is fully filled/last fill + return isPartiallyFillable + ? 'Limit order partially filled' + : 'Limit order filled'; + case 'liquidity': + // No longer used, should never happen + return 'Liquidity order filled'; + case 'twap': + return 'TWAP part is filled'; + default: + // Order class not properly configured + return 'Order filled'; + } +} From 7139d025b4f505e7af36b4e0a7269aeebc32e35a Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 5 Sep 2025 14:59:15 +0200 Subject: [PATCH 25/26] refactor: chunk orders --- .../OrdersRepositoryPostgres.ts | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts b/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts index 3d2c6954..5bf8e153 100644 --- a/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts +++ b/libs/repositories/src/repos/OrdersRepository/OrdersRepositoryPostgres.ts @@ -3,28 +3,55 @@ import { logger } from '@cowprotocol/shared'; import { Pool } from 'pg'; import { getOrderBookDbPool } from '../../datasources/orderBookDbPool'; import { bytesToHexString, hexStringToBytes } from '../../utils/bytesUtils'; +import { chunkArray } from '../../utils/chunkArray'; import { OrdersRepository } from './OrdersRepository'; +const LIMIT = 100; + export class OrdersRepositoryPostgres implements OrdersRepository { async getOrders( chainId: SupportedChainId, uids: string[] ): Promise>> { const prodDb = getOrderBookDbPool('prod', chainId); - const prodOrders = await this.fetchOrdersFromDb(uids, prodDb); - const orders: Partial[] = prodOrders || []; + + const prodChunks = chunkArray(uids, LIMIT); + + const prodOrders = await Promise.all( + prodChunks.map((chunk) => { + return this.fetchOrdersFromDb(chunk, prodDb); + }) + ); + + const orders: Partial[] = prodOrders.reduce[]>( + (acc, orders) => { + if (orders) { + acc.push(...orders); + } + return acc; + }, + [] + ); logger.info(`Prod orders: ${JSON.stringify(prodOrders, null, 2)}`); - if (prodOrders?.length !== uids.length) { + if (orders.length !== uids.length) { const barnDb = getOrderBookDbPool('barn', chainId); - const barnOrders = await this.fetchOrdersFromDb(uids, barnDb); - logger.info(`Barn orders: ${JSON.stringify(barnOrders, null, 2)}`); + const barnChunks = chunkArray(uids, LIMIT); + + const barnOrders = await Promise.all( + barnChunks.map((chunk) => { + return this.fetchOrdersFromDb(chunk, barnDb); + }) + ); + barnOrders?.forEach((orders) => { + if (orders) { + orders.push(...orders); + } + }); - if (barnOrders?.length) { - orders.push(...barnOrders); - } + logger.info(`Barn orders: ${JSON.stringify(barnOrders, null, 2)}`); } logger.info(`Orders: ${JSON.stringify(orders, null, 2)}`); @@ -44,7 +71,7 @@ export class OrdersRepositoryPostgres implements OrdersRepository { const byteaUids = uids.map(hexStringToBytes); // Note: add more fields as needed const query = ` - SELECT uid, partially_fillable FROM orders WHERE uid = ANY($1) LIMIT 1000 + SELECT uid, partially_fillable FROM orders WHERE uid = ANY($1) LIMIT ${LIMIT} `; const result = await db.query(query, [byteaUids]); From 97d9d930d6baa637ed888cffcb169bcc52540e60 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 5 Sep 2025 15:00:45 +0200 Subject: [PATCH 26/26] fix: add colomn to separate title from summary --- .../src/producers/trade/fromTradeToNotification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts index a5357015..2124698d 100644 --- a/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts +++ b/apps/notification-producer/src/producers/trade/fromTradeToNotification.ts @@ -53,7 +53,7 @@ export async function fromTradeToNotification(props: { const title = getOrderTitle(appData, isPartiallyFillable); - const fullMessage = `${title} ${summary}`; + const fullMessage = `${title}: ${summary}`; const message = `Account: ${owner}`; const url = orderUid ? getExplorerUrl(chainId, orderUid) : undefined;