Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion scripts/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ CANISTER_ID_NNS_DAPP=qoctq-giaaa-aaaaa-aaaea-cai
CANISTER_ID_SPAM_FILTER=xhfiu-7yaaa-aaaal-qvdja-cai
CANISTER_ID_SELF=mc7vh-sqaaa-aaaai-q33na-cai
EXTRA_ICP_SWAP_URL='https://uvevg-iyaaa-aaaak-ac27q-cai.raw.ic0.app'
EXTRA_KONG_SWAP_URL='https://api.kongswap.io/api'
EXTRA_PLAUSIBLE_DOMAIN_URL='nns.internetcomputer.org'
EOF

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export const ICP_SWAP_URL = process.env.EXTRA_ICP_SWAP_URL;
export const KONG_SWAP_URL = process.env.EXTRA_KONG_SWAP_URL;
export const PLAUSIBLE_DOMAIN_URL = process.env.EXTRA_PLAUSIBLE_DOMAIN_URL;

// Support and documentation URLs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { parseExchangeRateResponse, useExchangeRate } from './useExchangeRate';
export { parseIcpSwapTickers, useIcpSwapPrices } from './useIcpSwapPrices';
export { parseKongSwapTickers, useKongSwapPrices } from './useKongSwapPrices';
export { TickerPricesSource, useTickerPrices } from './useTickerPrices';
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { describe, expect, it } from 'vitest';

import { CANISTER_ID_ICP_LEDGER } from '@constants/canisterIds';

import { parseExchangeRateResponse } from './useExchangeRate';

describe('parseExchangeRateResponse', () => {
it('Parses a valid exchange rate response with both current and one_day_ago.', () => {
const result = parseExchangeRateResponse({
current: [
{
rate_e8s: 227_079_136n,
timestamp_seconds: 1_774_870_800n,
updated_at_seconds: 1_774_870_850n,
},
],
one_day_ago: [
{
rate_e8s: 222_988_450n,
timestamp_seconds: 1_774_784_400n,
updated_at_seconds: 1_774_870_864n,
},
],
});

expect(result.size).toBe(1);
expect(result.get(CANISTER_ID_ICP_LEDGER!)).toEqual({
_name: 'ICP',
icp: 1,
usd: 2.27079136,
previousUsd: 2.2298845,
});
});

it('Parses a response without one_day_ago.', () => {
const result = parseExchangeRateResponse({
current: [
{
rate_e8s: 227_079_136n,
timestamp_seconds: 1_774_870_800n,
updated_at_seconds: 1_774_870_850n,
},
],
one_day_ago: [],
});

expect(result.get(CANISTER_ID_ICP_LEDGER!)).toEqual({
_name: 'ICP',
icp: 1,
usd: 2.27079136,
previousUsd: undefined,
});
});

it('Throws when current rate is missing.', () => {
expect(() =>
parseExchangeRateResponse({
current: [],
one_day_ago: [],
}),
).toThrow('No current exchange rate available');
});

it('Returns undefined previousUsd when one_day_ago rate is invalid.', () => {
const result = parseExchangeRateResponse({
current: [
{
rate_e8s: 227_079_136n,
timestamp_seconds: 1_774_870_800n,
updated_at_seconds: 1_774_870_850n,
},
],
one_day_ago: [
{
rate_e8s: 0n,
timestamp_seconds: 1_774_784_400n,
updated_at_seconds: 1_774_870_864n,
},
],
});

expect(result.get(CANISTER_ID_ICP_LEDGER!)).toEqual({
_name: 'ICP',
icp: 1,
usd: 2.27079136,
previousUsd: undefined,
});
});

it('Throws when rate is zero.', () => {
expect(() =>
parseExchangeRateResponse({
current: [
{
rate_e8s: 0n,
timestamp_seconds: 1_774_870_800n,
updated_at_seconds: 1_774_870_850n,
},
],
one_day_ago: [],
}),
).toThrow('Invalid exchange rate');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { isNullish, nonNullish } from '@dfinity/utils';
import { useQuery } from '@tanstack/react-query';

import type { IcpExchangeRateResponse } from '@declarations/governance-app-backend/governance-app-backend.did';

import { CANISTER_ID_ICP_LEDGER } from '@constants/canisterIds';
import { E8S } from '@constants/extra';
import { useGovernanceAppCanister } from '@hooks/addressBook/useGovernanceAppCanister';
import { TokenPrices } from '@typings/tokenPrices';
import { QUERY_KEYS } from '@utils/query';

type Props = {
enabled?: boolean;
retryCount?: number;
};

export const useExchangeRate = ({ enabled = true, retryCount = 3 }: Props) => {
const canisterStatus = useGovernanceAppCanister();

return useQuery<TokenPrices>({
queryKey: [QUERY_KEYS.GOVERNANCE_APP_BACKEND.EXCHANGE_RATE],
queryFn: async () => {
if (!canisterStatus.ready) {
throw new Error('Canister not ready');
}

const response = await canisterStatus.canister.service.get_icp_to_usd_exchange_rate();
return parseExchangeRateResponse(response);
},
enabled: enabled && canisterStatus.ready,
retry: (failureCount) => failureCount < retryCount,
});
};

export const parseExchangeRateResponse = (response: IcpExchangeRateResponse): TokenPrices => {
if (isNullish(CANISTER_ID_ICP_LEDGER)) throw new Error('ICP ledger canister ID is not defined');

const currentRate = response.current[0];
Comment thread
yhabib marked this conversation as resolved.
if (isNullish(currentRate)) throw new Error('No current exchange rate available');

const icpPriceUsd = Number(currentRate.rate_e8s) / E8S;
if (icpPriceUsd <= 0 || !Number.isFinite(icpPriceUsd)) throw new Error('Invalid exchange rate');

const oneDayAgoRate = response.one_day_ago[0];
let previousUsd: number | undefined;
if (nonNullish(oneDayAgoRate)) {
const value = Number(oneDayAgoRate.rate_e8s) / E8S;
if (value > 0 && Number.isFinite(value)) {
previousUsd = value;
}
}

const result: TokenPrices = new Map();
result.set(CANISTER_ID_ICP_LEDGER, { _name: 'ICP', icp: 1, usd: icpPriceUsd, previousUsd });
Comment thread
yhabib marked this conversation as resolved.

return result;
};

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useExchangeRate } from './useExchangeRate';
import { useIcpSwapPrices } from './useIcpSwapPrices';
import { useKongSwapPrices } from './useKongSwapPrices';

export enum TickerPricesSource {
KONG_SWAP = 'KongSwap',
XRC = 'XRC',
ICP_SWAP = 'IcpSwap',
}

export const useTickerPrices = () => {
const icpSwapPrices = useIcpSwapPrices({ enabled: true, retryCount: 1 });
const useFallback = icpSwapPrices.isError;
const kongSwapPrices = useKongSwapPrices({ enabled: useFallback, retryCount: 3 });
const exchangeRate = useExchangeRate({ enabled: true, retryCount: 1 });
const useFallback = exchangeRate.isError;
const icpSwapPrices = useIcpSwapPrices({ enabled: useFallback, retryCount: 1 });

return {
// Fallback to KongSwap if IcpSwap fails.
tickerPrices: useFallback ? kongSwapPrices : icpSwapPrices,
tickerPricesSource: useFallback ? TickerPricesSource.KONG_SWAP : TickerPricesSource.ICP_SWAP,
// Fallback to IcpSwap if the backend exchange rate fails.
tickerPrices: useFallback ? icpSwapPrices : exchangeRate,
tickerPricesSource: useFallback ? TickerPricesSource.ICP_SWAP : TickerPricesSource.XRC,
};
};
6 changes: 4 additions & 2 deletions src/governance-app-frontend/src/common/hooks/useTvlValue.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CANISTER_ID_ICP_LEDGER } from '@constants/canisterIds';
import { E8Sn } from '@constants/extra';
import { useGovernanceMetrics } from '@hooks/governance';
import { useIcpSwapPrices } from '@hooks/tickers/useIcpSwapPrices';
import { useTickerPrices } from '@hooks/tickers';
import { bigIntDiv } from '@utils/bigInt';

export const useTvlValue = () => {
Expand All @@ -10,7 +10,9 @@ export const useTvlValue = () => {
isLoading: isMetricsLoading,
isError: isMetricsError,
} = useGovernanceMetrics();
const { data: prices, isLoading: isPricesLoading, isError: isPricesError } = useIcpSwapPrices({});
const {
tickerPrices: { data: prices, isLoading: isPricesLoading, isError: isPricesError },
} = useTickerPrices();

const lockedIcpE8s = metrics?.response?.totalLockedE8s;
const icpPrice = CANISTER_ID_ICP_LEDGER ? prices?.get(CANISTER_ID_ICP_LEDGER)?.usd : undefined;
Expand Down
10 changes: 0 additions & 10 deletions src/governance-app-frontend/src/common/typings/kongSwap.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ export type TokenPrices = Map<
_name: string;
icp: number;
usd: number;
// Previous day's USD price. Only available when the backend canister is the exchange rate source.
previousUsd?: number;
}
>;
2 changes: 1 addition & 1 deletion src/governance-app-frontend/src/common/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const ICP_INDEX = {
};

const EXTERNAL_SERVICES = {
KONG_SWAP_PRICES: 'kongSwapPrices',
ICP_SWAP_PRICES: 'icpSwapPrices',
};

Expand All @@ -35,6 +34,7 @@ const NNS_DAPP = {

const GOVERNANCE_APP_BACKEND = {
ADDRESS_BOOK: 'governanceAppBackendAddressBook',
EXCHANGE_RATE: 'governanceAppBackendExchangeRate',
};

const SPAM_FILTER = {
Expand Down
Loading
Loading