Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c535d13
Add env var for vortex fee PEN percentage
ebma Dec 29, 2025
55fd32b
Add Zenlink ID mapping for PEN and USDC assets
ebma Dec 29, 2025
a2cc555
Implement PEN buyback swap on Zenlink DEX for vortex fee distribution
ebma Dec 29, 2025
f7c5f0c
Deduplicate code
ebma Dec 29, 2025
e811ea9
Initial plan
Copilot Dec 29, 2025
77c55b1
Remove incorrect @param rampDirection from JSDoc
Copilot Dec 29, 2025
cd80b6e
return expiresAt field upon ramp registration
gianfra-t Jan 2, 2026
2129863
add timeout to phase execution
gianfra-t Jan 2, 2026
e964232
use subscan api to check for extrinsic result info
gianfra-t Jan 2, 2026
6518cd6
idempotency for distribute fees v2
gianfra-t Jan 2, 2026
82d4534
live testing, adjustments
gianfra-t Jan 5, 2026
c390522
do not create recoverable errors for failed extrinsics
gianfra-t Jan 5, 2026
0b04bd7
improve error handling while polling Subscan
gianfra-t Jan 5, 2026
368b532
Update apps/api/src/api/services/phases/handlers/distribute-fees-hand…
gianfra-t Jan 5, 2026
32bad63
re-introduce handling of dispatch error upon extrinsic broadcast
gianfra-t Jan 5, 2026
8dc7a21
Merge branch 'phase-processor-improvements' of github.com:pendulum-ch…
gianfra-t Jan 5, 2026
fc57774
fixes, code improvements
gianfra-t Jan 5, 2026
9035805
Update apps/api/src/api/services/phases/phase-processor.ts
gianfra-t Jan 5, 2026
f71edd4
handle new sql query result, group by chain and date
gianfra-t Jan 6, 2026
aab773b
add new supabase functions
gianfra-t Jan 7, 2026
3e0393e
remove comments
gianfra-t Jan 8, 2026
07fc9e7
improvements
gianfra-t Jan 8, 2026
f949ed9
remove stately debugging tools
Sharqiewicz Jan 9, 2026
85d23ba
remove stately debugging tools
Sharqiewicz Jan 9, 2026
0457458
Merge pull request #997 from pendulum-chain/group-volume-by-chain
ebma Jan 9, 2026
89b6fab
set max phase execution time to 20 minutes
gianfra-t Jan 9, 2026
dd86217
Merge pull request #990 from pendulum-chain/phase-processor-improvements
gianfra-t Jan 9, 2026
9bf2d24
Merge pull request #1004 from pendulum-chain/chore/UI-fixes
Sharqiewicz Jan 9, 2026
b478a27
Merge branch 'staging' into 965-build-pen-token-buyback-for-vortex-tr…
ebma Jan 9, 2026
51bf4cf
Fix bug with exponential notation for large EUR onramp values
ebma Jan 9, 2026
5233217
Merge pull request #984 from pendulum-chain/965-build-pen-token-buyba…
ebma Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 60 additions & 56 deletions apps/api/src/api/controllers/metrics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,38 @@ import { cache } from "../services";
const CACHE_TTL_SECONDS = 5 * 60; // 5 minutes

export interface VolumeRow {
chain: string;
buy_usd: number;
sell_usd: number;
total_usd: number;
}

export interface DailyVolume extends VolumeRow {
export interface MonthlyVolume extends VolumeRow {
month: string;
}

export interface ChainVolume {
chain: string;
buy_usd: number;
sell_usd: number;
total_usd: number;
}

export interface DailyVolume {
day: string;
chains: ChainVolume[];
}

export interface MonthlyVolume extends VolumeRow {
export interface MonthlyVolume {
month: string;
chains: ChainVolume[];
}

export interface WeeklyVolume extends VolumeRow {
export interface WeeklyVolume {
week: string;
startDate: string;
endDate: string;
}

export interface VolumeData {
monthly: MonthlyVolume[];
weekly: WeeklyVolume[];
daily: DailyVolume[];
selectedMonth: string;
chains: ChainVolume[];
}

let supabaseClient: SupabaseClient | null = null;
Expand All @@ -50,9 +58,7 @@ function getSupabaseClient() {

const zeroVolume = (key: string, keyName: "day" | "month"): any => ({
[keyName]: key,
buy_usd: 0,
sell_usd: 0,
total_usd: 0
chains: []
});

async function getMonthlyVolumes(): Promise<MonthlyVolume[]> {
Expand All @@ -62,7 +68,7 @@ async function getMonthlyVolumes(): Promise<MonthlyVolume[]> {

try {
const supabase = getSupabaseClient();
const { data, error } = await supabase.rpc("get_monthly_volumes", { year_param: null });
const { data, error } = await supabase.rpc("get_monthly_volumes_by_chain", { year_param: null });
if (error) throw error;

const rawData = (data as MonthlyVolume[]) || [];
Expand Down Expand Up @@ -91,60 +97,58 @@ async function getMonthlyVolumes(): Promise<MonthlyVolume[]> {
}

async function getDailyVolumes(startDate: string, endDate: string): Promise<DailyVolume[]> {
const cacheKey = `daily-${startDate}-${endDate}`;
const cached = cache.get<DailyVolume[]>(cacheKey);
if (cached) return cached;

try {
const supabase = getSupabaseClient();
const { data, error } = await supabase.rpc("get_daily_volumes", { end_date: endDate, start_date: startDate });
if (error) throw error;

const rawData = (data as DailyVolume[]) || [];
const dataMap = new Map(rawData.map(row => [row.day, row]));

const current = new Date(startDate);
const end = new Date(endDate);
const volumes: DailyVolume[] = [];

while (current <= end) {
const dayStr = current.toISOString().slice(0, 10);
volumes.push(dataMap.get(dayStr) || zeroVolume(dayStr, "day"));
current.setDate(current.getDate() + 1);
}

cache.set(cacheKey, volumes, CACHE_TTL_SECONDS);
return volumes;
} catch (error: any) {
throw new Error("Could not calculate daily volumes: " + error.message);
const supabase = getSupabaseClient();
const { data, error } = await supabase.rpc("get_daily_volumes_by_chain", {
end_date: endDate,
start_date: startDate
});

if (error) throw error;

const rawData = (data as DailyVolume[]) || [];
const dataMap = new Map(rawData.map(row => [row.day, row]));

const current = new Date(startDate);
const end = new Date(endDate);
const volumes: DailyVolume[] = [];

while (current <= end) {
const dayStr = current.toISOString().slice(0, 10);
// If date is missing, return empty chains array instead of zeroed fields
volumes.push(dataMap.get(dayStr) || { chains: [], day: dayStr });
current.setDate(current.getDate() + 1);
}

return volumes;
}

function aggregateWeekly(daily: DailyVolume[]): WeeklyVolume[] {
const weeks: WeeklyVolume[] = [];

for (let i = 0; i < daily.length; i += 7) {
const chunk = daily.slice(i, i + 7);
const startDay = chunk[0];
const endDay = chunk[chunk.length - 1];

const startDate = new Date(startDay.day);
const endDate = new Date(endDay.day);

const startMonth = startDate.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
const endMonth = endDate.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
const weekLabel = `${startMonth} ${startDate.getUTCDate()} - ${endMonth} ${endDate.getUTCDate()}`;
const startDay = chunk[0].day;
const endDay = chunk[chunk.length - 1].day;

const chainTotals = new Map<string, any>();

chunk.forEach(day => {
day.chains.forEach((c: any) => {
const existing = chainTotals.get(c.chain) || { buy_usd: 0, chain: c.chain, sell_usd: 0, total_usd: 0 };
existing.buy_usd += c.buy_usd;
existing.sell_usd += c.sell_usd;
existing.total_usd += c.total_usd;
chainTotals.set(c.chain, existing);
});
});

weeks.push({
buy_usd: chunk.reduce((sum, d) => sum + d.buy_usd, 0),
endDate: endDay.day,
sell_usd: chunk.reduce((sum, d) => sum + d.sell_usd, 0),
startDate: startDay.day,
total_usd: chunk.reduce((sum, d) => sum + d.total_usd, 0),
week: weekLabel
chains: Array.from(chainTotals.values()),
endDate: endDay,
startDate: startDay,
week: `${startDay} to ${endDay}`
});
}

return weeks;
}

Expand Down
Loading