Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a1462d5
remove unnecessary export keywords
yushih Oct 7, 2025
23626e6
wip
yushih Oct 8, 2025
aaf8635
debounce search
yushih Oct 8, 2025
755ec1a
wip - fix types
yushih Oct 9, 2025
a30eba7
wip
yushih Oct 9, 2025
7938ea8
use different backend URL for different networks
yushih Oct 21, 2025
884038e
fix pool id
yushih Oct 24, 2025
fa69b7f
fix csp
yushih Oct 31, 2025
19c9fb7
biased pools for mainnet only
yushih Oct 31, 2025
9068515
recommend EMURB in place of EMUR8
yushih Nov 5, 2025
37e2119
fix
yushih Nov 5, 2025
351f302
lint fixes
vsubhuman Nov 10, 2025
e3748be
wrap epoch in string
vsubhuman Nov 10, 2025
602993a
fix social handles
vsubhuman Nov 10, 2025
c38fc0f
flow fix
vsubhuman Nov 10, 2025
4e29dcb
resolving homepage
vsubhuman Nov 10, 2025
6f8fd8f
flow fix
vsubhuman Nov 10, 2025
a09af2f
fix hook deps
vsubhuman Nov 10, 2025
8006fdc
fixes
vsubhuman Nov 10, 2025
300375e
Fixed sorting options
vsubhuman Nov 10, 2025
76ca7b7
search fixes
vsubhuman Nov 10, 2025
a74bfe9
fixed random
vsubhuman Nov 10, 2025
3f1acd9
fix for sort check
vsubhuman Nov 11, 2025
276dfc2
changed pool fee fields to use active instead of live
vsubhuman Nov 13, 2025
b7a60d3
fixed saturation
vsubhuman Nov 17, 2025
c7884ad
Removed unused component file
vsubhuman Nov 17, 2025
2d850e6
Merge branch 'master' into chore/new-api
vsubhuman Nov 17, 2025
ddf17a9
reverted search function call on clear
vsubhuman Nov 17, 2025
ba3dbdb
fixing jdenticon in case no pool icon
vsubhuman Nov 19, 2025
1d05b6e
fixing jdenticon in case no pool icon
vsubhuman Nov 19, 2025
3712875
updated deprecated actions
vsubhuman Nov 19, 2025
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
2 changes: 1 addition & 1 deletion .env.development
Original file line number Diff line number Diff line change
@@ -1 +1 @@
REACT_APP_METATAG_CSP=default-src 'self' http://localhost:3000 https://kit-free.fontawesome.com https://*.cexplorer.io https://fonts.gstatic.com/s/rubik/; object-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com/ https://kit-free.fontawesome.com/; script-src 'self'; connect-src https://*.cexplorer.io ws:
REACT_APP_METATAG_CSP=default-src 'self' http://localhost:3000 https://kit-free.fontawesome.com https://*.cexplorer.io https://fonts.gstatic.com/s/rubik/; object-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com/ https://kit-free.fontawesome.com/; script-src 'self'; connect-src https://*.emurgornd.com https://zero.yoroiwallet.com ws:
2 changes: 1 addition & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
REACT_APP_METATAG_CSP=default-src 'self' https://kit-free.fontawesome.com https://*.cexplorer.io https://fonts.gstatic.com/s/rubik/; object-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com/ https://kit-free.fontawesome.com/; script-src 'self'; connect-src https://*.cexplorer.io
REACT_APP_METATAG_CSP=default-src 'self' https://kit-free.fontawesome.com https://*.cexplorer.io https://fonts.gstatic.com/s/rubik/; object-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com/ https://kit-free.fontawesome.com/; script-src 'self'; connect-src https://*.emurgornd.com https://zero.yoroiwallet.com
INLINE_RUNTIME_CHUNK=false
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = {
es2020: true,
},
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
indent: ['error', 2, { SwitchCase: 1, ignoredNodes: ["TemplateLiteral"] }],
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
'react/jsx-closing-bracket-location': 1,
'react/jsx-one-expression-per-line': 'off',
Expand Down Expand Up @@ -49,6 +49,7 @@ module.exports = {
'jsx-a11y/label-has-associated-control': 0,
'jsx-a11y/control-has-associated-label': 0,
'jsx-a11y/media-has-caption': 0,
"template-curly-spacing" : 0,
},
plugins: ['import', 'promise', 'react', 'flowtype'],
};
166 changes: 100 additions & 66 deletions src/API/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import axios from 'axios';
import seedrandom from 'seedrandom';
import { BACKEND_URL } from '../manifestEnvs';
import { BACKEND_URL_FOR_PREPROD, BACKEND_URL_FOR_MAINNET } from '../manifestEnvs';

const backendUrl: string = BACKEND_URL;
const SATURATION = 76293289283071;

const BIAS_POOL_IDS = [
'dbda39c8d064ff9801e376f8350efafe67c07e9e9244dd613aee5125', // EMURA
// '359d3f8e355c873b0b5cae1e18eb12e44dcfc2ad212706d93ac314ab', // EMURB
'8efb053977341471256685b1069d67f4aca7166bc3f94e27ebad217f', // EMUR7
'0ef7aa564933ce75b695cdad66be4a39b43a22726de7c58908e0e033', // EMUR8
// '0ef7aa564933ce75b695cdad66be4a39b43a22726de7c58908e0e033', // EMUR8
'359d3f8e355c873b0b5cae1e18eb12e44dcfc2ad212706d93ac314ab', // EMURB
'2a8294ad7538b15353b9ffd81e26dafe846ffc3f6b9e331d4c1dc030', // YORO1
'b19f2d9498845652ae6eea5da77952b37e2bca9f59b2a98c56694cae', // YORO2
];
Expand All @@ -28,21 +28,22 @@ const brackets = [
{ startIndex: 53, positionGap: 27 },
];

export type HistBPE = {|
type HistBPE = {|
+val: string,
+time: string,
+e: number,
|};

export type SocialMediaHandles = {|
type SocialMediaHandles = {|
tw: ?string,
tg: ?string,
fb: ?string,
yt: ?string,
tc: ?string,
di: ?string,
gh: ?string,
icon: ?string,
homepage?: ?string,
icon?: ?string,
|};

export type Pool = {|
Expand All @@ -51,47 +52,37 @@ export type Pool = {|
+db_ticker: ?string, // may not have a ticker
+db_name: ?string, // may not have a name
+pool_pic: ?string, // may not have a pic
+fullname: ?string,
+fullname?: ?string,
+pledge: string,
+pledge_real: string, // not sure diff with "pledge"
+total_stake: string, // in lovelace
+total_size: number, // percentage of total
+total_size?: number, // percentage of total
+tax_fix: string, // fix tax in lovelace
+tax_ratio: string, // ratio tax in percentage
+tax_computed: number, // not sure
+tax_computed?: number, // not sure
+blocks_epoch: string,
+roa: string,
+hist_bpe: {| [string]: HistBPE |},
+hist_roa: any, // no examples yet. similar to bpe?
+score: number,
+hist_bpe?: {| [string]: HistBPE |},
+hist_roa?: any, // no examples yet. similar to bpe?
+score?: number,
+handles: SocialMediaHandles, // social media stuff
+last_rewards: string,
+position: number,
+color_roa: string, // hsl(240,95%,95%)
+color_stake: string, // hsl(240,95%,95%)
+color_fees: string, // hsl(240,95%,95%)
+color_pledge: string, // hsl(240,95%,95%)
+last_rewards?: string,
+position?: number,
+color_roa?: string, // hsl(240,95%,95%)
+color_stake?: string, // hsl(240,95%,95%)
+color_fees?: string, // hsl(240,95%,95%)
+color_pledge?: string, // hsl(240,95%,95%)
+saturation: number,
|};

export type World = {|
+epoch: string,
+slot: string,
+stake: string,
+supply: number,
+pools: string,
+price: number,
+delegators: string,
type World = {|
+saturation: number,
|};

export const Sorting = Object.freeze({
TICKER: 'ticker',
SCORE: 'score',
ROA: 'roa',
POOL_SIZE: 'poolSize',
SATURATION: 'saturation',
COSTS: 'costs',
PLEDGE: 'pledge',
BLOCKS: 'blocks',
});
Expand All @@ -102,7 +93,7 @@ export const SortingDirections = Object.freeze({
});

export type SortingEnum = $Values<typeof Sorting>;
export type SortingDirEnum = $Values<typeof SortingDirections>;
type SortingDirEnum = $Values<typeof SortingDirections>;

export type SearchParams = {|
limit?: number,
Expand All @@ -111,44 +102,90 @@ export type SearchParams = {|
sortDirection?: SortingDirEnum,
|};

export type ApiPoolsResponse = {|
world?: World,
pools?: {| [string]: Pool |},
type ApiPoolsResponse = {|
world: World,
pools: Array<Pool>,
|};

const toPoolArray: (?{| [string]: Pool |}) => Array<Pool> = (pools) => {
if (pools == null) return [];
return Object.keys(pools)
.map((key) => pools[key])
.filter((x) => x != null);
};
function transformData(poolsResponse) {
return {
world: {
saturation: SATURATION,
},
pools: poolsResponse?.data?.data?.map((pool) => (
{
id: pool.pool_id_hash_raw,
id_bech: pool.pool_id,
db_ticker: pool.pool_name.ticker,
db_name: pool.pool_name.name,
pool_pic: `https://ix.cexplorer.io/${pool.pool_id}`,
pledge: String(pool.pool_update.active.pledge),
pledge_real: String(pool.pledged),
total_stake: String(pool.live_stake),
tax_fix: String(pool.pool_update.live.fixed_cost),
tax_ratio: String(pool.pool_update.live.margin),
blocks_epoch: String(pool.blocks.epoch),
roa: String(pool.stats.lifetime.roa),
handles: {
tw: pool.pool_name.extended?.twitter_handle ?? undefined,
tg: pool.pool_name.extended?.telegram_handle ?? undefined,
fb: pool.pool_name.extended?.facebook_handle ?? undefined,
yt: pool.pool_name.extended?.youtube_handle ?? undefined,
tc: pool.pool_name.extended?.twitch_handle ?? undefined,
di: pool.pool_name.extended?.discord_handle ?? undefined,
gh: pool.pool_name.extended?.github_handle ?? undefined,
homepage: pool.pool_name.homepage ?? undefined,
},
saturation: pool.live_stake / SATURATION,
}
)) ?? [],
};
}

export function getPools(body: SearchParams): Promise<ApiPoolsResponse> {
function convertSortingToBackendSorting(sorting: ?SortingEnum): string {
if (sorting === Sorting.SCORE) return 'ranking';
if (sorting === Sorting.ROA) return 'roa_lifetime';
if (sorting === Sorting.POOL_SIZE) return 'live_stake';
if (sorting === Sorting.PLEDGE) return 'pledge';
if (sorting === Sorting.BLOCKS) return 'blocks';
return 'ranking';
}

function getPools(network: 'mainnet' | 'preprod', body: SearchParams, bias: ?string = null): Promise<ApiPoolsResponse> {
const requestBody = {
...{ search: '', sort: Sorting.SCORE, limit: 250 },
...{ limit: 250 },
...body,
...{ sort: convertSortingToBackendSorting(body.sort) }
};

const encodeForm = (data) => {
return (Object.keys(data): any)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&');
};
const searchParams = new URLSearchParams();
if (requestBody.sort === 'ranking') {
searchParams.append('order', 'ranking');
}
if (requestBody.limit) {
searchParams.append('limit', String(requestBody.limit));
}
if (requestBody.sortDirection) {
searchParams.append('sort', requestBody.sortDirection);
}
if (requestBody.search) {
searchParams.append('name', requestBody.search);
}
if (bias) {
searchParams.append('poolId', bias);
}
const backendUrl = {
preprod: BACKEND_URL_FOR_PREPROD,
mainnet: BACKEND_URL_FOR_MAINNET,
}[network];

return axios(`${backendUrl}`, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'post',
data: encodeForm(requestBody),
})
return axios(`${backendUrl}?${searchParams.toString()}`)
.then((response) => {
const poolsResponse: ApiPoolsResponse = response.data;
return poolsResponse;
return transformData(response.data);
})
.catch((error) => {
console.error('API::getPools Error: ', error);
return {
pools: {},
};
return transformData(null);
});
}

Expand Down Expand Up @@ -181,15 +218,16 @@ export type ListBiasedPoolsResponse = {|
|};

export async function listBiasedPools(
network: 'mainnet' | 'preprod',
externalSeed: string,
searchParams: SearchParams,
): Promise<ListBiasedPoolsResponse> {
const unbiasedPoolsResponse = await getPools(searchParams);
const originalPools = toPoolArray(unbiasedPoolsResponse.pools);
const unbiasedPoolsResponse = await getPools(network, searchParams);
const originalPools = unbiasedPoolsResponse.pools;

const saturationLimit = unbiasedPoolsResponse.world?.saturation;

if (searchParams.search || searchParams.sort === Sorting.TICKER) {
if (searchParams.search || searchParams.sort !== Sorting.SCORE || network !== 'mainnet') {
// If user searched or sorted explicitly - then we don't bias
return { pools: originalPools, saturationLimit };
}
Expand All @@ -202,9 +240,9 @@ export async function listBiasedPools(
const internalSeed = tail(p1?.id) + tail(p2?.id) + tail(p3?.id);

try {
const biasedPoolsResponse = await getPools({ search: BIAS_POOLS_SEARCH_QUERY });
const biasedPoolsResponse = await getPools(network, ({}: any), BIAS_POOLS_SEARCH_QUERY);
if (!biasedPoolsResponse) return { pools: unbiasedPools, saturationLimit };
const biasedPools = toPoolArray(biasedPoolsResponse.pools)
const biasedPools = biasedPoolsResponse.pools
.filter((x) => x.id && BIAS_POOL_IDS.indexOf(x.id) >= 0)
.sort((a, b) => {
// this sorting is to ensure that changes in the backend response order is not affecting the final ordering
Expand Down Expand Up @@ -242,7 +280,3 @@ export async function listBiasedPools(
return { pools: unbiasedPools, saturationLimit };
}
}

export function listPools(): Promise<ApiPoolsResponse> {
return getPools(({}: any));
}
1 change: 1 addition & 0 deletions src/API/yoroi.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const YoroiCallback = (
};

export const SendFirstAdapool = (firstPool: Object): void => {
if (!firstPool) return;
const poolInfo = {
id: firstPool.id,
name: `[${firstPool.db_ticker}] ${firstPool.db_name}`,
Expand Down
3 changes: 2 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Node } from 'react';
import GlobalStyle from './helpers/globalStyles';
import Home from './containers/Home';
import HomeRevamp from './containers/HomeRevamp';
import type { UrlParams } from './containers/Home';
import type { UrlParams } from './types';

const parseIds = (array: ?string): Array<string> => {
if (array == null) return [];
Expand All @@ -25,6 +25,7 @@ const extractParams = (locationSearch: string): UrlParams => {
layout: params.get('layout'),
bias: params.get('bias'),
theme: params.get('theme'),
network: params.get('network') === 'preprod' ? 'preprod' : 'mainnet',
};
};

Expand Down
6 changes: 3 additions & 3 deletions src/components/DesktopTableRevamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function DesktopTableRevamp({
id: 0,
label: 'Ticker and name',
textInfo: null,
value: Sorting.TICKER,
value: null,
},
{
id: 1,
Expand All @@ -154,13 +154,13 @@ function DesktopTableRevamp({
id: 3,
label: 'Saturation',
textInfo: 'How close the pool is to its limit',
value: Sorting.SATURATION,
value: null,
},
{
id: 4,
label: 'Costs',
textInfo: 'Tax ratio + Fix',
value: Sorting.COSTS,
value: null,
},
{
id: 5,
Expand Down
Loading