Skip to content

Commit

Permalink
feat: enable op rewards claim (#949)
Browse files Browse the repository at this point in the history
  • Loading branch information
amateima authored Jan 16, 2024
1 parent 65d73f6 commit 4d752be
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 116 deletions.
95 changes: 95 additions & 0 deletions src/hooks/useUnclaimedProofs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useQuery } from "react-query";
import { BigNumber } from "ethers";
import { utils } from "@across-protocol/sdk-v2";

import { useConnection } from "hooks";
import { fetchIsClaimed, fetchAirdropProofs } from "utils/merkle-distributor";
import { getUnclaimedProofsQueryKey, rewardProgramTypes } from "utils";

export function useUnclaimedProofs(rewardsType: rewardProgramTypes) {
const { isConnected, account } = useConnection();

return useQuery(
getUnclaimedProofsQueryKey(rewardsType, account),
() => fetchUnclaimedProofs(rewardsType, account),
{
enabled: isConnected && !!account,
}
);
}

export function useUnclaimedReferralProofs() {
const { isConnected, account } = useConnection();

return useQuery(
getUnclaimedProofsQueryKey("referrals", account),
() => fetchUnclaimedProofs("referrals", account),
{
enabled: isConnected && !!account,
}
);
}

export function useUnclaimedOpRewardsProofs() {
const { isConnected, account } = useConnection();

return useQuery(
getUnclaimedProofsQueryKey("op-rebates", account),
() => fetchUnclaimedProofs("op-rebates", account),
{
enabled: isConnected && !!account,
}
);
}

async function fetchUnclaimedProofs(
rewardsType: rewardProgramTypes,
account?: string
) {
const allProofs = await fetchAirdropProofs(rewardsType, account);
const isClaimedResults = await fetchIsClaimedForIndices(
allProofs,
rewardsType
);

const unclaimed = allProofs.filter(
(proof) =>
isClaimedResults.findIndex(
(isClaimedResult) =>
!isClaimedResult.isClaimed &&
proof.accountIndex === isClaimedResult.accountIndex &&
proof.windowIndex === isClaimedResult.windowIndex
) >= 0
);
const claimableAmount = unclaimed.reduce(
(sum, { amount }) => sum.add(amount),
BigNumber.from(0)
);
return {
claimableAmount,
unclaimed,
};
}

async function fetchIsClaimedForIndices(
indices: { accountIndex: number; windowIndex: number }[],
rewardsType: rewardProgramTypes
) {
const isClaimedResults = await utils.mapAsync(
indices,
async ({ accountIndex, windowIndex }) => {
const isClaimed = await fetchIsClaimed(
windowIndex,
accountIndex,
rewardsType
);
return {
isClaimed,
windowIndex,
accountIndex,
};
}
);

return isClaimedResults;
}
57 changes: 0 additions & 57 deletions src/hooks/useUnclaimedReferralProofs.ts

This file was deleted.

14 changes: 11 additions & 3 deletions src/hooks/useWalletTokenImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ import { useCallback } from "react";
import { ERC20__factory } from "utils/typechain";

import { useConnection } from "./useConnection";
import { useIsWrongNetwork } from "./useIsWrongNetwork";
import { ChainId } from "utils";

export function useWalletTokenImport() {
export function useWalletTokenImport(chainId?: ChainId) {
const { signer } = useConnection();
const { isWrongNetwork, isWrongNetworkHandler } = useIsWrongNetwork(chainId);
const importTokenIntoWallet = useCallback(
(address: string, symbol: string, decimals: number) =>
importTokenToWallet(signer)(address, symbol, decimals),
[signer]
);
const importTokenIntoWalletFromLookup = useCallback(
(address: string) => importTokenToWalletFromLookup(signer)(address),
[signer]
async (address: string) => {
if (isWrongNetwork) {
await isWrongNetworkHandler();
}
await importTokenToWalletFromLookup(signer)(address);
},
[signer, isWrongNetwork, isWrongNetworkHandler]
);
return { importTokenIntoWallet, importTokenIntoWalletFromLookup };
}
Expand Down
35 changes: 31 additions & 4 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,27 @@ export class ConfigClient {
"0xE50b2cEAC4f60E840Ae513924033E753e2366487"
);
}
getOpRewardsMerkleDistributorAddress(): string {
return (
process.env.REACT_APP_OP_REWARDS_MERKLE_DISTRIBUTOR_ADDRESS ||
"0xc8b31410340d57417bE62672f6B53dfB9de30aC2"
);
}
getOpRewardsMerkleDistributorChainId(): number {
return parseInt(
process.env.REACT_APP_OP_REWARDS_MERKLE_DISTRIBUTOR_CHAIN_ID || "10"
);
}
getAcrossTokenAddress(): string {
return (
process.env.REACT_APP_ACROSS_TOKEN_ADDRESS ||
this.config.acrossTokenAddress ||
"0x44108f0223A3C3028F5Fe7AEC7f9bb2E66beF82F"
);
}
getOpTokenAddress(): string {
return "0x4200000000000000000000000000000000000042";
}
getClaimAndStakeAddress(): string {
return (
process.env.REACT_APP_CLAIM_AND_STAKE_ADDRESS ||
Expand All @@ -186,10 +200,23 @@ export class ConfigClient {
signer ?? providerUtils.getProvider(this.getHubPoolChainId());
return AcceleratingDistributor__factory.connect(address, provider);
}
getMerkleDistributor(signer?: Signer): AcrossMerkleDistributor {
const address = this.getMerkleDistributorAddress();
const provider =
signer ?? providerUtils.getProvider(this.getHubPoolChainId());
getMerkleDistributor(
rewardsType: constants.rewardProgramTypes,
signer?: Signer
): AcrossMerkleDistributor {
let address =
rewardsType === "referrals"
? this.getMerkleDistributorAddress()
: this.getOpRewardsMerkleDistributorAddress();

let provider =
signer?.provider ??
providerUtils.getProvider(
rewardsType === "referrals"
? this.getHubPoolChainId()
: this.getOpRewardsMerkleDistributorChainId()
);

return AcrossMerkleDistributor__factory.connect(address, provider);
}
getClaimAndStake(signer?: Signer): ClaimAndStake {
Expand Down
23 changes: 19 additions & 4 deletions src/utils/merkle-distributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
airdropWindowIndex,
rewardsApiUrl,
referralsStartWindowIndex,
rewardProgramTypes,
} from "utils/constants";

export type AmountBreakdown = {
Expand Down Expand Up @@ -36,9 +37,10 @@ const config = getConfig();

export async function fetchIsClaimed(
windowIndex: number,
accountIndex: number
accountIndex: number,
rewardsType: rewardProgramTypes
) {
const merkleDistributor = config.getMerkleDistributor();
const merkleDistributor = config.getMerkleDistributor(rewardsType);
return merkleDistributor.isClaimed(windowIndex, accountIndex);
}

Expand All @@ -60,17 +62,30 @@ export async function fetchAirdropProof(account?: string) {
return Object.keys(data).length ? (data as AirdropRecipient) : undefined;
}

export async function fetchReferralProofs(account?: string) {
export async function fetchAirdropProofs(
rewardsType: rewardProgramTypes,
account?: string
) {
if (!account) {
return [];
}

const startWindowIndex =
rewardsType === "referrals" ? referralsStartWindowIndex : 0;
let rewardsTypeQuery = "";

if (rewardsType === "referrals") {
rewardsTypeQuery = "referral-rewards";
} else if (rewardsType === "op-rebates") {
rewardsTypeQuery = "op-rewards";
}
const { data } = await axios.get<AirdropRecipient[]>(
`${rewardsApiUrl}/airdrop/merkle-distributor-proofs`,
{
params: {
address: account,
startWindowIndex: referralsStartWindowIndex,
startWindowIndex,
rewardsType: rewardsTypeQuery,
},
}
);
Expand Down
7 changes: 7 additions & 0 deletions src/utils/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,10 @@ export function isAirdropClaimedQueryKey(
) {
return ["is-airdrop-claimed", account, airdropWindowIndex];
}

export function getUnclaimedProofsQueryKey(
rewardsType: rewardProgramTypes,
account?: string
) {
return [rewardsType, "unclaimed", account];
}
3 changes: 2 additions & 1 deletion src/views/Airdrop/hooks/useIsAirdropClaimed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function useIsAirdropClaimed() {
if (airdropRecipientQuery.data) {
return fetchIsClaimed(
airdropWindowIndex,
airdropRecipientQuery.data.accountIndex
airdropRecipientQuery.data.accountIndex,
"referrals"
);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@ type Props = {
};

export function ClaimRewardsModal({ isOpen, onExit, program }: Props) {
const {
importTokenHandler,
unclaimedReferralProofsQuery,
claimMutation,
token,
} = useClaimModal(program);
const { importTokenHandler, unclaimedProofsQuery, claimMutation, token } =
useClaimModal(program);

const disableButton =
unclaimedReferralProofsQuery.isLoading ||
BigNumber.from(
unclaimedReferralProofsQuery.data?.claimableAmount ?? 0
).isZero() ||
unclaimedProofsQuery.isLoading ||
BigNumber.from(unclaimedProofsQuery.data?.claimableAmount ?? 0).isZero() ||
claimMutation.isLoading;

return (
Expand All @@ -43,23 +37,25 @@ export function ClaimRewardsModal({ isOpen, onExit, program }: Props) {
mobile: "bottom",
}}
>
<Alert status="warn">
Claiming your ACX will reset your tier to Copper and referral rate to{" "}
{rewardTiers[0].referralRate * 100}%.
</Alert>
{program === "referrals" && (
<Alert status="warn">
Claiming your ACX will reset your tier to Copper and referral rate to{" "}
{rewardTiers[0].referralRate * 100}%.
</Alert>
)}
<ClaimableBoxInnerWrapper>
<ClaimableBox>
<Text size="lg" color="white-70">
Claimable rewards
</Text>
<Text color="white-100">
{unclaimedReferralProofsQuery.isLoading ? (
{unclaimedProofsQuery.isLoading ? (
"Loading..."
) : (
<IconText>
{formatEther(
unclaimedReferralProofsQuery.data?.claimableAmount || "0"
) + " ACX"}
unclaimedProofsQuery.data?.claimableAmount || "0"
) + ` ${token.symbol}`}
<Icon src={token.logoURI} />
</IconText>
)}
Expand All @@ -78,7 +74,7 @@ export function ClaimRewardsModal({ isOpen, onExit, program }: Props) {
</ClaimableBoxInnerWrapper>
<AddToWalletWrapper>
<Text size="md" color="white-70">
Can't find the ACX token in your wallet? &nbsp;
Can't find the {token.symbol} token in your wallet? &nbsp;
</Text>
<AddToWalletLink onClick={importTokenHandler}>
Click here to add it.
Expand Down
2 changes: 1 addition & 1 deletion src/views/RewardsProgram/hooks/useACXReferralsProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
rewardTiers,
} from "utils";
import { useMemo } from "react";
import { useUnclaimedReferralProofs } from "hooks/useUnclaimedReferralProofs";
import { useUnclaimedReferralProofs } from "hooks/useUnclaimedProofs";
import { BigNumber } from "ethers";

export function useACXReferralsProgram() {
Expand Down
Loading

2 comments on commit 4d752be

@vercel
Copy link

@vercel vercel bot commented on 4d752be Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

goerli-frontend-v2 – ./

goerli-frontend-v2.vercel.app
goerli-frontend-v2-uma.vercel.app
goerli-frontend-v2-git-master-uma.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 4d752be Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.