Skip to content

Commit

Permalink
feat: additional v2 contract hooks (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
martines3000 authored Sep 2, 2024
1 parent 8b88556 commit 2136eb2
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 27 deletions.
2 changes: 1 addition & 1 deletion apps/frontend-v2/src/components/DashboardView/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Header = () => {
return getTotalSuppliedBalance(
suppliedBalance,
userCollateralAssets ?? {},
priceData ?? {}
priceData?.prices ?? {}
);
}, [userSupplyBorrow, userCollateralAssets, priceData]);

Expand Down
34 changes: 31 additions & 3 deletions apps/frontend-v2/src/components/DashboardView/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useSupplyCollateral } from '@/hooks';
import { usePrice, useSupplyCollateral } from '@/hooks';
import { useSupplyBase } from '@/hooks/useSupplyBase';
import { useWithdrawBase } from '@/hooks/useWithdrawBase';
import { useWithdrawCollateral } from '@/hooks/useWithdrawCollateral';
import { ACTION_TYPE, useMarketStore } from '@/stores';
import { TOKENS_BY_SYMBOL } from '@/utils';
import { TOKENS_BY_SYMBOL, TOKENS_LIST } from '@/utils';
import BigNumber from 'bignumber.js';
import React from 'react';
import { Button } from '../ui/button';
Expand All @@ -23,21 +26,46 @@ export const Input = () => {
changeActionTokenAssetId(TOKENS_BY_SYMBOL.USDC.assetId);
};

const { data: priceData } = usePrice(TOKENS_LIST.map((i) => i.assetId));

const { mutate: supplyCollateral } = useSupplyCollateral({
actionTokenAssetId,
});

const { mutate: withdrawCollateral } = useWithdrawCollateral({
actionTokenAssetId,
});

const { mutate: supplyBase } = useSupplyBase();

const { mutate: withdrawBase } = useWithdrawBase();

const handleSubmit = () => {
switch (action) {
case ACTION_TYPE.SUPPLY: {
if (actionTokenAssetId === TOKENS_BY_SYMBOL.USDC.assetId) {
supplyBase(tokenAmount);
} else {
supplyCollateral(tokenAmount);
}
break;
}
case ACTION_TYPE.WITHDRAW:
case ACTION_TYPE.WITHDRAW: {
if (!priceData) return;

if (actionTokenAssetId === TOKENS_BY_SYMBOL.USDC.assetId) {
withdrawBase({
tokenAmount,
priceUpdateData: priceData.priceUpdateData,
});
} else {
withdrawCollateral({
tokenAmount,
priceUpdateData: priceData.priceUpdateData,
});
}
break;
}
case ACTION_TYPE.BORROW:
break;
case ACTION_TYPE.REPAY:
Expand Down
99 changes: 76 additions & 23 deletions apps/frontend-v2/src/hooks/usePrice.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,86 @@
import { TOKENS_BY_ASSET_ID, TOKENS_BY_PRICE_FEED } from '@/utils';
import { Market, type PriceDataUpdateInput } from '@/contract-types/Market';
import {
CONTRACT_ADDRESSES,
TOKENS_BY_ASSET_ID,
TOKENS_BY_PRICE_FEED,
} from '@/utils';
import { HermesClient } from '@pythnetwork/hermes-client';
import {
PYTH_CONTRACT_ADDRESS_SEPOLIA,
PythContract,
} from '@pythnetwork/pyth-fuel-js';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { arrayify } from 'fuels';
import { useProvider } from './useProvider';

export const usePrice = (assetIds: string[]) => {
const hermesClient = new HermesClient('https://hermes.pyth.network');

const fetchPrice = async (assetIds: string[]) => {
const priceUpdates = await hermesClient.getLatestPriceUpdates(
assetIds.map((assetId) => TOKENS_BY_ASSET_ID[assetId].priceFeed)
);
if (
!priceUpdates ||
!priceUpdates.parsed ||
priceUpdates.parsed.length === 0
) {
throw new Error('Failed to fetch price');
}
const previousPrices: Record<string, BigNumber> = {};
priceUpdates.parsed.forEach((current) => {
const currentAssetId = TOKENS_BY_PRICE_FEED[current.id].assetId;
previousPrices[currentAssetId] = BigNumber(current.price.price).times(
BigNumber(10).pow(BigNumber(current.price.expo))
);
});
return previousPrices;
};
const provider = useProvider();

return useQuery({
queryKey: ['priceOf', assetIds],
queryFn: () => fetchPrice(assetIds),
queryKey: ['pythPrices', assetIds],
queryFn: async () => {
if (!provider) return;

const priceFeedIds = assetIds.map(
(assetId) => TOKENS_BY_ASSET_ID[assetId].priceFeed
);

// Fetch price udpates from Hermes client
const priceUpdates =
await hermesClient.getLatestPriceUpdates(priceFeedIds);

if (
!priceUpdates ||
!priceUpdates.parsed ||
priceUpdates.parsed.length === 0
) {
throw new Error('Failed to fetch price');
}

// Fetch updateFee
const pythContract = new PythContract(
PYTH_CONTRACT_ADDRESS_SEPOLIA,
provider
);
const marketContract = new Market(CONTRACT_ADDRESSES.market, provider);

const buffer = Buffer.from(priceUpdates.binary.data[0], 'hex');
const updateData = [arrayify(buffer)];

const { value: fee } = await marketContract.functions
.update_fee(updateData)
.addContracts([pythContract])
.get();

// Prepare the PriceDateUpdateInput object
const priceUpdateData: PriceDataUpdateInput = {
update_fee: fee,
publish_times: priceUpdates.parsed.map(
(parsedPrice) => parsedPrice.price.publish_time
),
price_feed_ids: priceFeedIds,
update_data: updateData,
};

// Format prices to BigNumber
const prices: Record<string, BigNumber> = {};

priceUpdates.parsed.forEach((parsedPrice) => {
const currentAssetId = TOKENS_BY_PRICE_FEED[parsedPrice.id].assetId;
prices[currentAssetId] = BigNumber(parsedPrice.price.price).times(
BigNumber(10).pow(BigNumber(parsedPrice.price.expo))
);
});

return {
prices,
priceUpdateData,
};
},
refetchInterval: 5000,
enabled: !!provider,
});
};
65 changes: 65 additions & 0 deletions apps/frontend-v2/src/hooks/useSupplyBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Market } from '@/contract-types';
import { CONTRACT_ADDRESSES, EXPLORER_URL, TOKENS_BY_SYMBOL } from '@/utils';
import { useAccount, useWallet } from '@fuels/react';
import { useMutation } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { toast } from 'react-toastify';

export const useSupplyBase = () => {
const { wallet } = useWallet();
const { account } = useAccount();

return useMutation({
mutationKey: ['supplyBase', account],
mutationFn: async (tokenAmount: BigNumber) => {
if (!wallet || !account) {
return;
}

const marketContract = new Market(CONTRACT_ADDRESSES.market, wallet);

const amount = new BigNumber(tokenAmount).times(
10 ** TOKENS_BY_SYMBOL.USDC.decimals
);

const { waitForResult } = await marketContract.functions
.supply_base()
.callParams({
forward: {
assetId: TOKENS_BY_SYMBOL.USDC.assetId,
amount: amount.toString(),
},
})
.call();

const transactionResult = await toast.promise(waitForResult(), {
pending: {
render: 'Transaction is pending...',
},
});

return transactionResult.transactionId;
},
onSuccess: (data) => {
if (data) {
toast(
<div>
Transaction successful:{' '}
<a
target="_blank"
rel="noreferrer"
className="underline cursor-pointer text-blue-500"
href={`${EXPLORER_URL}/${data}`}
>
{data}
</a>
</div>
);
}
},
onError: (error) => {
console.log(error);
toast('Error');
},
});
};
86 changes: 86 additions & 0 deletions apps/frontend-v2/src/hooks/useWithdrawBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Market } from '@/contract-types';
import type { PriceDataUpdateInput } from '@/contract-types/Market';
import {
CONTRACT_ADDRESSES,
EXPLORER_URL,
FUEL_ETH_BASE_ASSET_ID,
TOKENS_BY_SYMBOL,
} from '@/utils';
import { useAccount, useWallet } from '@fuels/react';
import {
PYTH_CONTRACT_ADDRESS_SEPOLIA,
PythContract,
} from '@pythnetwork/pyth-fuel-js';
import { useMutation } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { toast } from 'react-toastify';

export const useWithdrawBase = () => {
const { wallet } = useWallet();
const { account } = useAccount();

return useMutation({
mutationKey: ['withdrawBase', account],
mutationFn: async ({
tokenAmount,
priceUpdateData,
}: {
tokenAmount: BigNumber;
priceUpdateData: PriceDataUpdateInput;
}) => {
if (!wallet || !account) {
return;
}
const pythContract = new PythContract(
PYTH_CONTRACT_ADDRESS_SEPOLIA,
wallet
);

const marketContract = new Market(CONTRACT_ADDRESSES.market, wallet);

const amount = new BigNumber(tokenAmount).times(
10 ** TOKENS_BY_SYMBOL.USDC.decimals
);

const { waitForResult } = await marketContract.functions
.withdraw_base(amount.toString(), priceUpdateData)
.callParams({
forward: {
amount: priceUpdateData.update_fee,
assetId: FUEL_ETH_BASE_ASSET_ID,
},
})
.addContracts([pythContract])
.call();

const transactionResult = await toast.promise(waitForResult(), {
pending: {
render: 'Transaction is pending...',
},
});

return transactionResult.transactionId;
},
onSuccess: (data) => {
if (data) {
toast(
<div>
Transaction successful:{' '}
<a
target="_blank"
rel="noreferrer"
className="underline cursor-pointer text-blue-500"
href={`${EXPLORER_URL}/${data}`}
>
{data}
</a>
</div>
);
}
},
onError: (error) => {
console.log(error);
toast('Error');
},
});
};
Loading

0 comments on commit 2136eb2

Please sign in to comment.