Skip to content

Commit

Permalink
Collateral price updates for transactions (#256)
Browse files Browse the repository at this point in the history
* feat: collateral price updates

* feat: new price ids for base

* fix: staleness tolerance issue

* feat: price update for stale prices

* feat: update prices for write calls

* fix: deps issue
  • Loading branch information
Rickk137 authored Apr 26, 2024
1 parent 94f20f5 commit 9292a2e
Show file tree
Hide file tree
Showing 20 changed files with 164 additions and 156 deletions.
6 changes: 3 additions & 3 deletions liquidity/lib/useAccountCollateral/useAccountCollateral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useDefaultProvider, useNetwork } from '@snx-v3/useBlockchain';
import { Wei, wei } from '@synthetixio/wei';
import { useCollateralTypes } from '@snx-v3/useCollateralTypes';
import { erc7412Call } from '@snx-v3/withERC7412';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';
import { useAllCollateralPriceUpdates } from '../useCollateralPriceUpdates';

export type AccountCollateralType = {
tokenAddress: string;
Expand Down Expand Up @@ -68,7 +68,7 @@ export function useAccountCollateral({
const collateralTypes = useCollateralTypes(includeDelegationOff);
const tokenAddresses = collateralTypes.data?.map((c) => c.tokenAddress) ?? [];
const provider = useDefaultProvider();
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();

return useQuery({
queryKey: [
Expand Down Expand Up @@ -106,7 +106,7 @@ export function useAccountSpecificCollateral(accountId?: string, collateralAddre
const { data: CoreProxy } = useCoreProxy();
const { network } = useNetwork();
const provider = useDefaultProvider();
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();

return useQuery({
queryKey: [
Expand Down
1 change: 0 additions & 1 deletion liquidity/lib/useClearDebt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"main": "index.ts",
"version": "0.0.1",
"dependencies": {
"@snx-v3/fetchPythPrices": "workspace:*",
"@snx-v3/isBaseAndromeda": "workspace:*",
"@snx-v3/tsHelpers": "workspace:*",
"@snx-v3/txnReducer": "workspace:*",
Expand Down
24 changes: 7 additions & 17 deletions liquidity/lib/useClearDebt/useClearDebt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { useGasSpeed } from '@snx-v3/useGasSpeed';
import { notNil } from '@snx-v3/tsHelpers';
import { withERC7412 } from '@snx-v3/withERC7412';
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
import { useSpotMarketProxy } from '../useSpotMarketProxy';
import { USDC_BASE_MARKET, getRepayerContract } from '@snx-v3/isBaseAndromeda';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';

export const DEBT_REPAYER_ABI = [
{
Expand Down Expand Up @@ -49,6 +49,7 @@ export const useClearDebt = ({
const { data: CoreProxy } = useCoreProxy();
const { data: SpotMarketProxy } = useSpotMarketProxy();
const { data: collateralPriceIds } = useAllCollateralPriceIds();
const { data: priceUpdateTx } = useCollateralPriceUpdates();

const signer = useSigner();
const { network } = useNetwork();
Expand Down Expand Up @@ -97,24 +98,13 @@ export const useClearDebt = ({

const callsPromise = Promise.all([depositDebtToRepay, burn].filter(notNil));

const walletAddress = await signer.getAddress();
const [calls, gasPrices] = await Promise.all([callsPromise, getGasPrice({ provider })]);

const collateralPriceCallsPromise = fetchPriceUpdates(
collateralPriceIds,
network.isTestnet
).then((signedData) =>
priceUpdatesToPopulatedTx(walletAddress, collateralPriceIds, signedData)
);

const [calls, gasPrices, collateralPriceCalls] = await Promise.all([
callsPromise,
getGasPrice({ provider }),
collateralPriceCallsPromise,
]);

const allCalls = collateralPriceCalls.concat(calls);
if (priceUpdateTx) {
calls.unshift(priceUpdateTx as any);
}

const erc7412Tx = await withERC7412(network, allCalls, 'useRepay', CoreProxy.interface);
const erc7412Tx = await withERC7412(network, calls, 'useRepay', CoreProxy.interface);

const gasOptionsForTransaction = formatGasPriceForTransaction({
gasLimit: erc7412Tx.gasLimit,
Expand Down
1 change: 1 addition & 0 deletions liquidity/lib/useCollateralPriceUpdates/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@snx-v3/isBaseAndromeda": "workspace:*",
"@snx-v3/useBlockchain": "workspace:*",
"@snx-v3/withERC7412": "workspace:*",
"@synthetixio/v3-contracts": "workspace:*",
"@tanstack/react-query": "^5.8.3",
"ethers": "^5.7.2"
}
Expand Down
116 changes: 100 additions & 16 deletions liquidity/lib/useCollateralPriceUpdates/useCollateralPriceUpdates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { ethers } from 'ethers';
import { useNetwork } from '@snx-v3/useBlockchain';
import { Network, useDefaultProvider, useNetwork, useWallet } from '@snx-v3/useBlockchain';

import { EvmPriceServiceConnection } from '@pythnetwork/pyth-evm-js';
import { offchainMainnetEndpoint } from '@snx-v3/constants';
import { ERC7412_ABI } from '@snx-v3/withERC7412';
import { getsPythWrapper, isBaseAndromeda } from '@snx-v3/isBaseAndromeda';
import { importMulticall3 } from '@synthetixio/v3-contracts';

const priceIds = [
'0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
Expand Down Expand Up @@ -34,34 +35,117 @@ const priceIds = [
'0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17',
'0x09f7c1d7dfbb7df2b8fe3d3d87ee94a2259d212da4f30c1f0540d066dfa44723',
'0x8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac3026',
'0x9a4df90b25497f66b1afb012467e316e801ca3d839456db028892fe8c70c8016',
'0x5fcf71143bb70d41af4fa9aa1287e2efd3c5911cee59f909f915c9f61baacb1e',
];

const priceService = new EvmPriceServiceConnection(offchainMainnetEndpoint);

export const useCollateralPriceUpdates = () => {
const getPriceUpdates = async (
priceIds: string[],
stalenessTolerance: number,
network: Network | null
) => {
const signedOffchainData = await priceService.getPriceFeedsUpdateData(priceIds);
const updateType = 1;
const data = ethers.utils.defaultAbiCoder.encode(
['uint8', 'uint64', 'bytes32[]', 'bytes[]'],
[updateType, stalenessTolerance, priceIds, signedOffchainData]
);
const erc7412Interface = new ethers.utils.Interface(ERC7412_ABI);

return {
//pyth wrapper
to: getsPythWrapper(network?.id),
data: erc7412Interface.encodeFunctionData('fulfillOracleQuery', [data]),
value: priceIds.length * 10,
};
};
export const useAllCollateralPriceUpdates = () => {
const { network } = useNetwork();

return useQuery({
queryKey: [`${network?.id}-${network?.preset}`, 'price-updates', priceIds.join(',')],
queryKey: [`${network?.id}-${network?.preset}`, 'all-price-updates', priceIds.join(',')],
enabled: isBaseAndromeda(network?.id, network?.preset),
queryFn: async () => {
const updateType = 1,
stalenessTolerance = 60;
const stalenessTolerance = 60;

const signedOffchainData = await priceService.getPriceFeedsUpdateData(priceIds);

const data = ethers.utils.defaultAbiCoder.encode(
['uint8', 'uint64', 'bytes32[]', 'bytes[]'],
[updateType, stalenessTolerance, priceIds, signedOffchainData]
);
const erc7412Interface = new ethers.utils.Interface(ERC7412_ABI);
const tx = await getPriceUpdates(priceIds, stalenessTolerance, network);

return {
//pyth wrapper
to: getsPythWrapper(network?.id),
data: erc7412Interface.encodeFunctionData('fulfillOracleQuery', [data]),
value: priceIds.length * 10,
...tx,
value: tx.value * 10,
};
},
});
};

export const useCollateralPriceUpdates = () => {
const { network } = useNetwork();
const provider = useDefaultProvider();
const { activeWallet } = useWallet();

return useQuery({
queryKey: [`${network?.id}-${network?.preset}`, 'price-updates'],
enabled: isBaseAndromeda(network?.id, network?.preset),
queryFn: async () => {
const stalenessTolerance = 3300;
if (!network) {
return;
}

try {
const { address: multicallAddress, abi: multiCallAbi } = await importMulticall3(
network.id,
network.preset
);

const multicallInterface = new ethers.utils.Interface(multiCallAbi);
const pythInterface = new ethers.utils.Interface([
'function getLatestPrice(bytes32 priceId, uint256 stalenessTolerance) external view returns (int256)',
]);

const txs = [
...priceIds.map((priceId) => ({
target: getsPythWrapper(network.id),
callData: pythInterface.encodeFunctionData('getLatestPrice', [
priceId,
stalenessTolerance,
]),
value: 0,
requireSuccess: false,
})),
];

const getPricesTx = multicallInterface.encodeFunctionData('aggregate3Value', [txs]);

const result = await provider?.call({
data: getPricesTx,
to: multicallAddress,
});

const decodedMultiCall: { success: boolean }[] = multicallInterface.decodeFunctionResult(
'aggregate3Value',
result || ''
)[0];

const outdatedPriceIds: string[] = [];

decodedMultiCall.forEach(({ success }, i) => {
if (!success) {
outdatedPriceIds.push(priceIds[i]);
}
});

if (outdatedPriceIds.length) {
return {
from: activeWallet?.address,
...(await getPriceUpdates(outdatedPriceIds, stalenessTolerance, network)),
};
}
} catch (error) {
return null;
}
},
});
};
4 changes: 2 additions & 2 deletions liquidity/lib/useCollateralPrices/useCollateralPrices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Wei, { wei } from '@synthetixio/wei';
import { useDefaultProvider, useNetwork } from '@snx-v3/useBlockchain';
import { erc7412Call } from '@snx-v3/withERC7412';
import { useCollateralTypes } from '@snx-v3/useCollateralTypes';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';
import { useAllCollateralPriceUpdates } from '../useCollateralPriceUpdates';

const PriceSchema = ZodBigNumber.transform((x) => wei(x));

Expand Down Expand Up @@ -47,7 +47,7 @@ export const useCollateralPrices = () => {

const provider = useDefaultProvider();
const collateralAddresses = collateralData?.map((x) => x.tokenAddress);
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();

return useQuery({
enabled: Boolean(CoreProxy && collateralAddresses && collateralAddresses?.length > 0),
Expand Down
2 changes: 0 additions & 2 deletions liquidity/lib/useDepositBaseAndromeda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
"main": "index.ts",
"version": "0.0.1",
"dependencies": {
"@snx-v3/fetchPythPrices": "workspace:*",
"@snx-v3/format": "workspace:*",
"@snx-v3/isBaseAndromeda": "workspace:*",
"@snx-v3/tsHelpers": "workspace:*",
"@snx-v3/txnReducer": "workspace:*",
"@snx-v3/useAllCollateralPriceIds": "workspace:*",
"@snx-v3/useApprove": "workspace:*",
"@snx-v3/useBlockchain": "workspace:*",
"@snx-v3/useCoreProxy": "workspace:*",
Expand Down
28 changes: 8 additions & 20 deletions liquidity/lib/useDepositBaseAndromeda/useDepositBaseAndromeda.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import { getGasPrice } from '@snx-v3/useGasPrice';
import { useGasSpeed } from '@snx-v3/useGasSpeed';
import { withERC7412 } from '@snx-v3/withERC7412';
import { notNil } from '@snx-v3/tsHelpers';
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
import { useSpotMarketProxy } from '../useSpotMarketProxy';
import { parseUnits } from '@snx-v3/format';
import { USDC_BASE_MARKET, getsUSDCAddress } from '@snx-v3/isBaseAndromeda';
import { approveAbi } from '@snx-v3/useApprove';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';

export const useDepositBaseAndromeda = ({
accountId,
Expand All @@ -38,7 +37,7 @@ export const useDepositBaseAndromeda = ({
const [txnState, dispatch] = useReducer(reducer, initialState);
const { data: CoreProxy } = useCoreProxy();
const { data: SpotMarketProxy } = useSpotMarketProxy();
const { data: collateralPriceUpdates } = useAllCollateralPriceIds();
const { data: priceUpdateTx } = useCollateralPriceUpdates();

const { gasSpeed } = useGasSpeed();

Expand All @@ -57,8 +56,7 @@ export const useDepositBaseAndromeda = ({
SpotMarketProxy &&
poolId &&
collateralTypeAddress &&
availableCollateral &&
collateralPriceUpdates
availableCollateral
)
) {
return;
Expand All @@ -67,7 +65,6 @@ export const useDepositBaseAndromeda = ({

try {
dispatch({ type: 'prompting' });
const walletAddress = await signer.getAddress();
const id = accountId ?? newAccountId;

// create account only when no account exists
Expand Down Expand Up @@ -111,24 +108,15 @@ export const useDepositBaseAndromeda = ({
[wrap, sUSDCApproval, createAccount, deposit, delegate].filter(notNil)
);

const collateralPriceCallsPromise = fetchPriceUpdates(
collateralPriceUpdates,
network?.isTestnet
).then((signedData) =>
priceUpdatesToPopulatedTx(walletAddress, collateralPriceUpdates, signedData)
);

const [calls, gasPrices, collateralPriceCalls] = await Promise.all([
callsPromise,
getGasPrice({ provider }),
collateralPriceCallsPromise,
]);
const [calls, gasPrices] = await Promise.all([callsPromise, getGasPrice({ provider })]);

const allCalls = collateralPriceCalls.concat(calls);
if (priceUpdateTx) {
calls.unshift(priceUpdateTx as any);
}

const erc7412Tx = await withERC7412(
network,
allCalls,
calls,
'useDepositBaseAndromeda',
CoreProxy.interface
);
Expand Down
4 changes: 2 additions & 2 deletions liquidity/lib/useLiquidityPosition/useLiquidityPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { loadAccountCollateral, AccountCollateralType } from '@snx-v3/useAccount
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
import { useUSDProxy } from '@snx-v3/useUSDProxy';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';
import { useAllCollateralPriceUpdates } from '../useCollateralPriceUpdates';

const PositionCollateralSchema = z.object({
value: ZodBigNumber.transform((x) => wei(x)).optional(), // This is currently only removed on base-goreli
Expand Down Expand Up @@ -80,7 +80,7 @@ export const useLiquidityPosition = ({
const { data: CoreProxy } = useCoreProxy();
const { data: UsdProxy } = useUSDProxy();
const { network } = useNetwork();
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();
const provider = useProviderForChain(network!);

return useQuery({
Expand Down
4 changes: 2 additions & 2 deletions liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { erc7412Call } from '@snx-v3/withERC7412';
import { keyBy } from '@snx-v3/tsHelpers';
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';
import { useAllCollateralPriceUpdates } from '../useCollateralPriceUpdates';

export type LiquidityPositionType = {
id: `${string}-${string}`;
Expand Down Expand Up @@ -43,7 +43,7 @@ export const useLiquidityPositions = ({ accountId }: { accountId?: string }) =>
const { data: pools } = usePools();
const { data: collateralTypes } = useCollateralTypes();
const { data: collateralPriceUpdates } = useAllCollateralPriceIds();
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();

const { network } = useNetwork();
const provider = useProviderForChain(network!);
Expand Down
4 changes: 2 additions & 2 deletions liquidity/lib/usePoolConfiguration/usePoolConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ethers } from 'ethers';
import { erc7412Call } from '@snx-v3/withERC7412';
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
import { useCollateralPriceUpdates } from '../useCollateralPriceUpdates';
import { useAllCollateralPriceUpdates } from '../useCollateralPriceUpdates';

export const MarketConfigurationSchema = z.object({
id: SmallIntSchema,
Expand All @@ -29,7 +29,7 @@ export const usePoolConfiguration = (poolId?: string) => {
const { data: CoreProxy } = useCoreProxy();
const { data: collateralPriceUpdates } = useAllCollateralPriceIds();
const provider = useDefaultProvider();
const { data: priceUpdateTx } = useCollateralPriceUpdates();
const { data: priceUpdateTx } = useAllCollateralPriceUpdates();

return useQuery({
enabled: Boolean(CoreProxy && poolId && collateralPriceUpdates),
Expand Down
Loading

0 comments on commit 9292a2e

Please sign in to comment.