Skip to content

Commit

Permalink
feat: implement Affiliates protocol interactions (#1074)
Browse files Browse the repository at this point in the history
  • Loading branch information
rosepuppy authored Oct 3, 2024
1 parent 5650d36 commit 2a552fd
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 55 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
"@cosmjs/tendermint-rpc": "^0.32.1",
"@datadog/browser-logs": "^5.23.3",
"@dydxprotocol/v4-abacus": "1.12.12",
"@dydxprotocol/v4-client-js": "1.6.1",
"@dydxprotocol/v4-client-js": "1.10.0",
"@dydxprotocol/v4-localization": "^1.1.213",
"@dydxprotocol/v4-proto": "^6.0.1",
"@dydxprotocol/v4-proto": "^7.0.0-dev.0",
"@emotion/is-prop-valid": "^1.3.0",
"@ethersproject/providers": "^5.7.2",
"@hugocxl/react-to-image": "^0.0.9",
Expand Down
18 changes: 6 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export enum LocalStorageKey {
EvmDerivedAddresses = 'dydx.EvmDerivedAddresses',
KeplrCompliance = 'dydx.KeplrCompliance',
SolDerivedAddresses = 'dydx.SolDerivedAddresses',
LatestReferrer = 'dydx.LatestReferrer',

// Gas
SelectedGasDenom = 'dydx.SelectedGasDenom',
Expand Down
40 changes: 25 additions & 15 deletions src/hooks/useAffiliatesInfo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import { useAccounts } from './useAccounts';
import { log } from '@/lib/telemetry';

import { useDydxClient } from './useDydxClient';

type AffiliatesMetadata = {
Expand All @@ -9,31 +10,40 @@ type AffiliatesMetadata = {
isAffiliate: boolean;
};

export const useAffiliatesInfo = () => {
const { dydxAddress } = useAccounts();
const { compositeClient } = useDydxClient();
export const useAffiliatesInfo = (dydxAddress?: string) => {
const { compositeClient, getAffiliateInfo } = useDydxClient();

const queryFn = async () => {
if (!compositeClient || !dydxAddress) {
return undefined;
}
const endpoint = `${compositeClient.indexerClient.config.restEndpoint}/v4/affiliates/metadata`;
const response = await fetch(`${endpoint}?address=${encodeURIComponent(dydxAddress)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const data = await response.json();
return data as AffiliatesMetadata | undefined;

try {
const response = await fetch(`${endpoint}?address=${encodeURIComponent(dydxAddress)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const affiliateInfo = await getAffiliateInfo(dydxAddress);

const data: AffiliatesMetadata | undefined = await response.json();
const isEligible =
Boolean(data?.isVolumeEligible) || Boolean(affiliateInfo?.isWhitelisted) || true;

return { metadata: data, affiliateInfo, isEligible };
} catch (error) {
log('useAffiliatesInfo', error, { endpoint });
throw error;
}
};

const { data, isFetched } = useQuery({
const query = useQuery({
queryKey: ['affiliatesMetadata', dydxAddress],
queryFn,
enabled: Boolean(compositeClient && dydxAddress),
});

return { data, isFetched };
return query;
};
16 changes: 16 additions & 0 deletions src/hooks/useDydxClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,20 @@ const useDydxClientContext = () => {
[compositeClient]
);

const getAffiliateInfo = useCallback(
async (address: string) => {
return compositeClient?.validatorClient.get.getAffiliateInfo(address);
},
[compositeClient]
);

const getReferredBy = useCallback(
async (address: string) => {
return compositeClient?.validatorClient.get.getReferredBy(address);
},
[compositeClient]
);

return {
// Client initialization
connect: setNetworkConfig,
Expand Down Expand Up @@ -518,6 +532,8 @@ const useDydxClientContext = () => {
getWithdrawalCapacityByDenom,
getValidators,
getAccountBalance,
getAffiliateInfo,
getReferredBy,

// vault methods
getMegavaultHistoricalPnl,
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useReferralAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export const useReferralAddress = (refCode: string) => {
return data?.address as string | undefined;
};

const { data, isFetched } = useQuery({
const query = useQuery({
queryKey: ['referralAddress', refCode],
queryFn,
enabled: Boolean(compositeClient && refCode),
});

return { data, isFetched };
return query;
};
36 changes: 36 additions & 0 deletions src/hooks/useReferredBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useQuery } from '@tanstack/react-query';

import { DydxAddress } from '@/constants/wallets';

import { log } from '@/lib/telemetry';

import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient';

export const useReferredBy = () => {
const { dydxAddress } = useAccounts();
const { getReferredBy, compositeClient } = useDydxClient();

const queryFn = async ({ queryKey }: { queryKey: (string | DydxAddress | undefined)[] }) => {
const [, address] = queryKey;
if (!address) {
return undefined;
}
try {
const affliateAddress = await getReferredBy(address);

return affliateAddress?.affiliateAddress;
} catch (error) {
log('useReferredBy', error);
return undefined;
}
};

const { data, isFetched } = useQuery({
queryKey: ['referredBy', dydxAddress],
queryFn,
enabled: Boolean(compositeClient && dydxAddress),
});

return { data, isFetched };
};
36 changes: 36 additions & 0 deletions src/hooks/useRegisterAffiliate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useQuery } from '@tanstack/react-query';

import { DydxAddress } from '@/constants/wallets';

import { log } from '@/lib/telemetry';

import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient';

export const useReferredBy = () => {
const { dydxAddress } = useAccounts();
const { getReferredBy } = useDydxClient();

const queryFn = async ({ queryKey }: { queryKey: (string | DydxAddress | undefined)[] }) => {
const [, address] = queryKey;
if (!address) {
return undefined;
}
try {
const affliateAddress = await getReferredBy(address);

return affliateAddress?.affiliateAddress;
} catch (error) {
log('useReferredBy', error);
return undefined;
}
};

const { data, isFetched } = useQuery({
queryKey: ['referredBy', dydxAddress],
queryFn,
enabled: Boolean(dydxAddress),
});

return { data, isFetched };
};
81 changes: 80 additions & 1 deletion src/hooks/useSubaccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type GovAddNewMarketParams,
type LocalWallet,
} from '@dydxprotocol/v4-client-js';
import { useMutation } from '@tanstack/react-query';
import Long from 'long';
import { shallowEqual } from 'react-redux';
import { formatUnits, parseUnits } from 'viem';
Expand All @@ -32,6 +33,8 @@ import { DydxAddress, WalletType } from '@/constants/wallets';

import { clearSubaccountState } from '@/state/account';
import { getBalances, getSubaccountOrders } from '@/state/accountSelectors';
import { removeLatestReferrer } from '@/state/affiliates';
import { getLatestReferrer } from '@/state/affiliatesSelector';
import { useAppDispatch, useAppSelector } from '@/state/appTypes';
import { openDialog } from '@/state/dialogs';
import {
Expand All @@ -55,6 +58,7 @@ import { hashFromTx } from '@/lib/txUtils';
import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient';
import { useGovernanceVariables } from './useGovernanceVariables';
import { useReferredBy } from './useReferredBy';
import { useTokenConfigs } from './useTokenConfigs';

type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
Expand Down Expand Up @@ -286,7 +290,6 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall
const balanceAmount = parseFloat(balance.amount);
const shouldDeposit = balanceAmount - AMOUNT_RESERVED_FOR_GAS_USDC > 0;
const shouldWithdraw = balanceAmount - AMOUNT_USDC_BEFORE_REBALANCE <= 0;

if (shouldDeposit) {
await depositToSubaccount({
amount: balanceAmount - AMOUNT_RESERVED_FOR_GAS_USDC,
Expand Down Expand Up @@ -909,6 +912,79 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall
[localDydxWallet, compositeClient]
);

const registerAffiliate = useCallback(
async (affiliate: string) => {
if (!compositeClient) {
throw new Error('client not initialized');
}
if (!subaccountClient?.wallet?.address) {
throw new Error('wallet not initialized');
}
if (affiliate === subaccountClient?.wallet?.address) {
throw new Error('affiliate can not be the same as referree');
}
try {
const response = await compositeClient?.validatorClient.post.registerAffiliate(
subaccountClient,
affiliate
);
return response;
} catch (error) {
log('useSubaccount/registerAffiliate', error);
throw error;
}
},
[subaccountClient, compositeClient]
);

const latestReferrer = useAppSelector(getLatestReferrer);
const { data: referredBy, isFetched: isReferredByFetched } = useReferredBy();

const { mutateAsync: registerAffiliateMutate, isPending: isRegisterAffiliatePending } =
useMutation({
mutationFn: async (affiliate: string) => {
const tx = await registerAffiliate(affiliate);
dispatch(removeLatestReferrer());
return tx;
},
});

useEffect(() => {
if (!subaccountClient) return;

if (dydxAddress === latestReferrer) {
dispatch(removeLatestReferrer());
return;
}
if (
latestReferrer &&
dydxAddress &&
usdcCoinBalance &&
parseFloat(usdcCoinBalance.amount) > AMOUNT_USDC_BEFORE_REBALANCE &&
isReferredByFetched &&
!referredBy &&
!isRegisterAffiliatePending
) {
registerAffiliateMutate(latestReferrer);
}
}, [
latestReferrer,
dydxAddress,
registerAffiliateMutate,
usdcCoinBalance,
subaccountClient,
isReferredByFetched,
referredBy,
dispatch,
isRegisterAffiliatePending,
]);

useEffect(() => {
if (referredBy && latestReferrer) {
dispatch(removeLatestReferrer());
}
}, [referredBy, dispatch, latestReferrer]);

const getVaultAccountInfo = useCallback(async () => {
if (!compositeClient?.validatorClient) {
throw new Error('client not initialized');
Expand Down Expand Up @@ -983,6 +1059,9 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall
withdrawReward,
getWithdrawRewardFee,

// affiliates
registerAffiliate,

// vaults
getVaultAccountInfo,
depositToMegavault,
Expand Down
4 changes: 3 additions & 1 deletion src/state/_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import storage from 'redux-persist/lib/storage';
import abacusStateManager from '@/lib/abacus';

import { accountSlice } from './account';
import { affiliatesSlice } from './affiliates';
import { appSlice } from './app';
import appMiddleware from './appMiddleware';
import { assetsSlice } from './assets';
Expand All @@ -24,6 +25,7 @@ import { walletSlice } from './wallet';

const reducers = {
account: accountSlice.reducer,
affiliates: affiliatesSlice.reducer,
app: appSlice.reducer,
assets: assetsSlice.reducer,
configs: configsSlice.reducer,
Expand All @@ -45,7 +47,7 @@ const persistConfig = {
key: 'root',
version: 1,
storage,
whitelist: ['tradingView', 'wallet'],
whitelist: ['tradingView', 'wallet', 'affiliates'],
migrate: customCreateMigrate({ debug: process.env.NODE_ENV !== 'production' }),
};

Expand Down
Loading

0 comments on commit 2a552fd

Please sign in to comment.