Skip to content

Commit

Permalink
feat: reward aggregator
Browse files Browse the repository at this point in the history
  • Loading branch information
Marchand-Nicolas committed Oct 13, 2024
1 parent 2f12e15 commit 13bf6ff
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 108 deletions.
194 changes: 92 additions & 102 deletions components/discover/claimModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,145 +10,110 @@ import AppIcon from "./appIcon";
import TokenIcon from "./tokenIcon";
import { useNotification } from "@context/NotificationProvider";
import Loading from "@app/loading";
import { useAccount, useContractWrite } from "@starknet-react/core";
import { RewardsPerProtocol } from "../../types/backTypes";
import { getRewards } from "@services/apiService";
import { gweiToEth } from "@utils/feltService";
import { Call } from "../../types/frontTypes";

type RewardItem = {
appName: string,
currencies: { currencyName: string, value: number }[]
}
appName: string;
currencies: { currencyName: string; value: number }[];
};

type CurrencyRowProps = {
currencyName: string;
currencyValue: number;
}
};

type ClaimModalProps = {
closeModal: () => void;
showSuccess: () => void;
open: boolean;
};

const RewardComponent: FunctionComponent<RewardItem> = ({ appName, currencies }) => (
<div className="flex w-full justify-between items-center bg-background px-2 py-3 my-1 rounded-lg">
const RewardComponent: FunctionComponent<RewardItem> = ({
appName,
currencies,
}) => (
<div className="flex w-full justify-between items-center bg-background px-4 py-3 my-1 rounded-lg">
<div className="flex flex-row gap-4">
<AppIcon app={appName} />
<AppIcon
app={appName}
imageDimensions={{
width: 25,
height: 25,
}}
customStyle={{
border: ".5px solid #fff",
}}
/>
<Typography type={TEXT_TYPE.BODY_MIDDLE}>
{appName}
<span className="capitalize">{appName}</span>
</Typography>
</div>
<div className="flex w-fit flex-col items-end">
{currencies.map((currency, idx) => (
<CurrencyRow key={idx} currencyName={currency.currencyName} currencyValue={currency.value} />
<CurrencyRow
key={idx}
currencyName={currency.currencyName}
currencyValue={currency.value}
/>
))}
</div>
</div>
);

const CurrencyRow: FunctionComponent<CurrencyRowProps> = ({ currencyName, currencyValue }) => (
const CurrencyRow: FunctionComponent<CurrencyRowProps> = ({
currencyName,
currencyValue,
}) => (
<div className="flex flex-row items-center gap-4">
<Typography type={TEXT_TYPE.BODY_SMALL}>
{currencyValue < 1000 ? currencyValue : `${currencyValue / 1000}K`}
</Typography>
<Typography type={TEXT_TYPE.BODY_SMALL}>
{currencyName}
</Typography>
<Typography type={TEXT_TYPE.BODY_SMALL}>{currencyName}</Typography>
<TokenIcon token={currencyName} />
</div>
);

const ClaimModal: FunctionComponent<ClaimModalProps> = ({
closeModal,
showSuccess,
open
open,
}) => {
const [claimRewards, setClaimRewards] = useState<RewardItem[]>();
const [loading, setLoading] = useState<boolean>(true);
const { showNotification } = useNotification();
const { address } = useAccount();
const [rewards, setRewards] = useState<RewardsPerProtocol | null>(null);
const [calls, setCalls] = useState<Call[]>();
const { writeAsync: execute } = useContractWrite({
calls: calls || [],
});

const getClaimRewards = useCallback(async () => {
// TODO: Implement fetch from backend. Returning mock values.
try {
setLoading(true);
const rewards = [
{
appName: "EKUBO",
currencies: [
{ currencyName: "STRK", value: 11570 }
],
},
{
appName: "NOSTRA",
currencies: [
{ currencyName: "STRK", value: 12.124 },
{ currencyName: "ETH", value: 1.1245 }
],
},
{
appName: "zkLend",
currencies: [
{ currencyName: "USDT", value: 124.12 }
],
},
{
appName: "VESU",
currencies: [
{ currencyName: "STRK", value: 36 }
],
},
{
appName: "Nimbora",
currencies: [
{ currencyName: "STRK", value: 70.145 }
],
},
{
appName: "zkLend",
currencies: [
{ currencyName: "USDT", value: 124.12 }
],
},
{
appName: "VESU",
currencies: [
{ currencyName: "STRK", value: 36 }
],
},
{
appName: "Nimbora",
currencies: [
{ currencyName: "STRK", value: 70.145 }
],
},
];
const res = await new Promise<RewardItem[]>(resolve => setTimeout(() => resolve(rewards), 2000));
setClaimRewards(res);
setLoading(false);
} catch (error) {
useEffect(() => {
if (!address) return;
getRewards(address).then((res) => {
setRewards(res?.rewards);
setCalls(res?.calls);
setLoading(false);
showNotification("Error while fetching rewards", "error");
console.log("Error while fetching rewards", error);
}
}, []);
});
}, [address]);

const doClaimRewards = useCallback(async () => {
// TODO: Implement logic to claim rewards
try {
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 2000));
await execute();
setLoading(false);
closeModal();
showSuccess();
} catch (error) {
setLoading(false);
showNotification("Error while claiming rewards", "error");
console.log("Error while claiming rewards", error);
console.log("Error while claiming rewards", error, calls);
}
}, []);

useEffect(() => {
if (open) {
getClaimRewards();
}
}, [open, getClaimRewards]);
}, [calls]);

return (
<Modal
Expand All @@ -157,14 +122,19 @@ const ClaimModal: FunctionComponent<ClaimModalProps> = ({
aria-label="Reward claim success modal"
open={open}
>
<div className={`${styles.popup} !overflow-y-hidden !rounded-2xl !mt-0 !px-1 !py-1 md:!px-0 md:!py-0`}>
<div
className={`${styles.popup} !overflow-y-hidden !rounded-2xl !mt-0 !px-1 !py-1 md:!px-0 md:!py-0`}
>
<Loading isLoading={loading} loadingType="spinner">
<div className={`${styles.popupContent} !px-0 !pt-6 !pb-0 md:!px-12 md:!pt-14`}>
<div
className={`${styles.popupContent} !px-0 !pt-6 !pb-0 md:!px-12 md:!pt-14`}
>
<div className="flex w-full flex-col self-start">
<div className="flex w-full lg:flex-row flex-col-reverse lg:items-start items-center justify-between gap-4 md:pb-2">
<div className="flex flex-col gap-4 lg:text-left text-center">
<Typography type={TEXT_TYPE.BODY_MIDDLE}>
Collect your rewards from all supported protocols on Starknet
Collect your rewards from all supported protocols on
Starknet
</Typography>
<Typography type={TEXT_TYPE.H4}>
Claim Your Rewards
Expand All @@ -178,25 +148,45 @@ const ClaimModal: FunctionComponent<ClaimModalProps> = ({
style={{ transform: "rotateY(190deg)" }}
/>
</div>
<div className="flex w-full flex-col mt-4 max-h-80 overflow-auto">
{!claimRewards ?
<Typography type={TEXT_TYPE.BODY_DEFAULT}>No rewards available</Typography>
:
claimRewards.map((item, index) => (
<RewardComponent key={index} appName={item.appName} currencies={item.currencies} />
))}
<div className="flex w-full flex-col py-4 max-h-80 overflow-auto">
{!rewards ? (
<Typography type={TEXT_TYPE.BODY_DEFAULT}>
No rewards available
</Typography>
) : (
Object.keys(rewards)
.map((key) =>
rewards[key as keyof RewardsPerProtocol].length > 0 ? (
<RewardComponent
key={key}
appName={key}
currencies={rewards[
key as keyof RewardsPerProtocol
].map((reward) => {
return {
currencyName: reward.token_symbol,
value:
Math.round(
parseFloat(gweiToEth(reward.amount)) * 100
) / 100,
};
})}
/>
) : null
)
.flat()
)}
</div>
</div>
</div>
<div className={`${styles.bottomContent} !gap-6 !py-6 !px-5`}>
<div className="flex w-full justify-between items-center">
<button onClick={closeModal} aria-label="Cancel claiming rewards">
<Typography type={TEXT_TYPE.BODY_MIDDLE}>
Cancel
</Typography>
<Typography type={TEXT_TYPE.BODY_MIDDLE}>Cancel</Typography>
</button>
<div className="w-fit">
<Button disabled={claimRewards ? false : true}
<Button
disabled={calls ? false : true}
onClick={doClaimRewards}
>
Claim all
Expand All @@ -210,4 +200,4 @@ const ClaimModal: FunctionComponent<ClaimModalProps> = ({
);
};

export default ClaimModal;
export default ClaimModal;
Binary file modified public/nostra/favicon.ico
Binary file not shown.
21 changes: 21 additions & 0 deletions services/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
altProtocolStats,
pairStats,
lendStats,
Call,
} from "types/backTypes";

const baseurl = process.env.NEXT_PUBLIC_API_LINK;
Expand Down Expand Up @@ -411,3 +412,23 @@ export const getAltProtocolStats =
return null;
}
};

export const getRewards = async (address: string) => {
try {
const response = await fetch(`${baseurl}/defi/rewards?addr=${address}`);
const data = await response.json();
const rewards = data.rewards;
const calls = data.calls;
const parsedCalls = calls.map((call: Call) => {
return {
contractAddress: call.contractaddress,
entrypoint: call.entrypoint,
calldata: call.calldata,
};
});
return { rewards, calls: parsedCalls };
} catch (err) {
console.log("Error while fetching rewards", err);
return null;
}
};
28 changes: 23 additions & 5 deletions types/backTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export type CreateContract = {
desc: string;
href: string;
cta: string;
calls: object;
calls: object;
};

export type UpdateContract = {
Expand Down Expand Up @@ -491,22 +491,40 @@ export type AddUser = {
password: string;
};

export type CreateCustomApi = {
export type CreateCustomApi = {
quest_id: number;
name: string;
desc: string;
href: string;
cta: string;
api_url?: string;
regex?: string;
}
};

export type UpdateCustomApi = {
export type UpdateCustomApi = {
id: number;
name?: string;
desc?: string;
href?: string;
cta?: string;
api_url?: string;
regex?: string;
}
};

export type Reward = {
amount: string;
token_symbol: string;
};

export type RewardsPerProtocol = {
zklend: Reward[];
nostra: Reward[];
nimbora: Reward[];
ekubo: Reward[];
};

export type Call = {
contractaddress: string;
calldata: string[];
entrypoint: string;
};
8 changes: 7 additions & 1 deletion types/frontTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ type StepMap =
| { type: "Domain"; data: DomainInputType }
| { type: "Balance"; data: BalanceInputType }
| { type: "Contract"; data: ContractInputType }
| { type: "CustomApi"; data: CustomApiInputType }
| { type: "CustomApi"; data: CustomApiInputType };

type CustomInputType = typeof CustomInput;
type DiscordInputType = typeof DiscordInput;
Expand All @@ -341,3 +341,9 @@ type TaskType =
| "None";

type networks = "MAINNET" | "TESTNET";

export type Call = {
contractAddress: string;
calldata: string[];
entrypoint: string;
};

0 comments on commit 13bf6ff

Please sign in to comment.