From dc33ee94099c2b46cccad9bc86b7c7cf68aafd8e Mon Sep 17 00:00:00 2001 From: bayge Date: Thu, 25 Jul 2024 17:41:04 +0930 Subject: [PATCH] Add election of the airdropped token --- .../app/queries/addAirdropElection.ts | 25 ++++++ web/app.fluidity.money/app/queries/index.ts | 1 + .../app/queries/useFLYOwed.ts | 1 + .../$network/dashboard/airdrop/common.tsx | 79 ++++++++++++------- .../app/styles/dashboard/airdrop.css | 5 ++ .../app/styles/dashboard/airdrop.scss | 5 ++ .../util/chainUtils/ethereum/transaction.ts | 29 ++++++- .../contexts/EthereumProvider.tsx | 16 +++- .../contexts/FluidityFacade.tsx | 3 + 9 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 web/app.fluidity.money/app/queries/addAirdropElection.ts diff --git a/web/app.fluidity.money/app/queries/addAirdropElection.ts b/web/app.fluidity.money/app/queries/addAirdropElection.ts new file mode 100644 index 000000000..a3a1e288c --- /dev/null +++ b/web/app.fluidity.money/app/queries/addAirdropElection.ts @@ -0,0 +1,25 @@ +import { jsonPost } from "~/util"; + +const BaseUrl = + "https://duvlwhscy2.execute-api.ap-southeast-2.amazonaws.com/default/flu-create-airdrop-elections"; + +type RequestAirdropElection = { + address: string; + option: number; + sig: string; +}; + +type ResponseAirdropElection = { + address: string; + updated: string; + error: string; +}; + +export const addAirdropElection = (address: string, option: number, sig: string) => { + const body = { address, option, sig }; + return jsonPost( + BaseUrl, + body, + {} + ); +}; diff --git a/web/app.fluidity.money/app/queries/index.ts b/web/app.fluidity.money/app/queries/index.ts index 824cc43c3..ac6a745be 100644 --- a/web/app.fluidity.money/app/queries/index.ts +++ b/web/app.fluidity.money/app/queries/index.ts @@ -12,3 +12,4 @@ export * from "./addReferralCode"; export * from "./useLootboxConfig"; export * from "./useLootBottles"; export * from "./useFLYOwed"; +export * from "./addAirdropElection"; diff --git a/web/app.fluidity.money/app/queries/useFLYOwed.ts b/web/app.fluidity.money/app/queries/useFLYOwed.ts index 2ebc762b7..9e13a74f6 100644 --- a/web/app.fluidity.money/app/queries/useFLYOwed.ts +++ b/web/app.fluidity.money/app/queries/useFLYOwed.ts @@ -17,6 +17,7 @@ type ResponseFLYOwedForAddress = { amount: number; updated: string; bottles: ResponseFLYOwedForAddressBottle[]; + allocated: boolean; error: string; }; diff --git a/web/app.fluidity.money/app/routes/$network/dashboard/airdrop/common.tsx b/web/app.fluidity.money/app/routes/$network/dashboard/airdrop/common.tsx index 08038d3b0..b6356c066 100644 --- a/web/app.fluidity.money/app/routes/$network/dashboard/airdrop/common.tsx +++ b/web/app.fluidity.money/app/routes/$network/dashboard/airdrop/common.tsx @@ -1,11 +1,5 @@ /* eslint-disable no-irregular-whitespace */ -import type { - StakingRatioRes, - StakingDepositsRes, -} from "~/util/chainUtils/ethereum/transaction"; -import type AugmentedToken from "~/types/AugmentedToken"; - import { useState, useEffect, useContext, useMemo, useCallback } from "react"; import BN from "bn.js"; import { @@ -36,13 +30,19 @@ import { Provider, Modal, } from "@fluidity-money/surfing"; +import type { + StakingRatioRes, + StakingDepositsRes, +} from "~/util/chainUtils/ethereum/transaction"; +import { AirdropElection } from "~/util/chainUtils/ethereum/transaction"; +import type AugmentedToken from "~/types/AugmentedToken"; import { addDecimalToBn, getTokenAmountFromUsd, getUsdFromTokenAmount, } from "~/util/chainUtils/tokens"; import { dayDifference } from "."; -import { useFLYOwedForAddress, Referral } from "~/queries"; +import { useFLYOwedForAddress, addAirdropElection, Referral } from "~/queries"; import { BottleTiers } from "../../query/dashboard/airdrop"; import { AnimatePresence, @@ -55,7 +55,6 @@ import FluidityFacadeContext from "contexts/FluidityFacade"; import { FlyStakingContext } from "contexts/FlyStakingProvider"; import { CopyGroup } from "~/components/ReferralModal"; import ConnectWalletModal from "~/components/ConnectWalletModal"; -import FLYClaimSubmitModal from "~/components/FLYClaimSubmitModal"; import { shorthandAmountFormatter } from "~/util"; // Epoch length @@ -1767,7 +1766,7 @@ const RecapModal = ({ }, }; - const { address } = useContext(FluidityFacadeContext); + const { address, signAirdropElection } = useContext(FluidityFacadeContext); const { toggleVisibility: flyStakingModalToggleVisibility } = useContext(FlyStakingContext); @@ -1777,10 +1776,13 @@ const RecapModal = ({ const [walletModalVisibility, setWalletModalVisibility] = useState(false); const [flyClaimModalState, setFlyClaimModalState] = useState< - "none" | "claim" | "stake" + "none" | "claim" | "stake" | "convert" >("none"); + const [isMidSigningAirdropElection, setIsMidSigningAirdropElection] = useState(false); + const [electMessage, setElectMessage] = useState(""); const [flyAmountOwed, setFLYAmountOwed] = useState(0); + const [isFLYAllocated, setIsFLYAllocated] = useState(false); const [showTGEDetails, setShowTGEDetails] = useState(true); @@ -1818,9 +1820,11 @@ const RecapModal = ({ console.warn(`Invalid response for airdrop request: ${resp}`); return; } - const { amount, error } = resp; + const { amount, allocated, error } = resp; if (error) throw new Error(`Airdrop request error: ${error}`); setFLYAmountOwed(amount); + setIsFLYAllocated(allocated); + if (allocated) setElectMessage("You have already elected to claim, stake, or convert!"); setCheckYourEligibilityButtonEnabled(true); } })(); @@ -1858,9 +1862,37 @@ const RecapModal = ({ ); }; - const handleClaimYourFly = (type: "claim" | "stake") => { + const handleClaimYourFly = async (type: "claim" | "stake" | "convert") => { + if (!address) return; setFlyClaimModalState(type); - // Get the user's address.by + if (!signAirdropElection || isMidSigningAirdropElection) return; + setIsMidSigningAirdropElection(true); + var option: number; + switch (type) { + case "claim": + option = AirdropElection.Claim; + break; + case "stake": + option = AirdropElection.Stake; + break; + case "convert": + option = AirdropElection.ConvertToSpn; + break; + } + const signature = await signAirdropElection(option); + console.log("signature for election", signature); + if (!signature) { + setIsMidSigningAirdropElection(true); + setElectMessage("Bad signature!"); + return; + } + const { error } = await addAirdropElection(address, option, signature); + if (error) { + console.error("error setting airdrop election", error); + setElectMessage("Error setting option for distribution. Try again, and create a Discord ticket if another issue occurs."); + return; + } + setElectMessage("Success setting!"); }; const [termsAndConditionsModalVis, setTermsAndConditionsModalVis] = @@ -1884,20 +1916,20 @@ const RecapModal = ({ const ClaimButtonsSpread = () => (
handleClaimYourFly("claim")} > Claim your FLY handleClaimYourFly("stake")} > Stake your $FLY airdrop handleClaimYourFly("stake")} + disabled={isFLYAllocated || isMidSigningAirdropElection} + onClick={() => handleClaimYourFly("convert")} > Convert to $SPN points @@ -1936,6 +1968,7 @@ const RecapModal = ({ {numberToCommaSeparated(flyAmountOwed)}
+ {electMessage}
@@ -2095,18 +2128,6 @@ const RecapModal = ({ - - setWalletModalVisibility(true)} - flyAmount={flyAmountOwed} - visible={flyClaimModalState !== "none"} - mode={flyClaimModalState === "none" ? "claim" : flyClaimModalState} - accumulatedPoints={day1Points} - close={() => setFlyClaimModalState("none")} - onStakingComplete={handleClaimStakingModalComplete} - onClaimComplete={handleClaimStakingModalComplete} - /> -
{/* Recap Heading */}
diff --git a/web/app.fluidity.money/app/styles/dashboard/airdrop.css b/web/app.fluidity.money/app/styles/dashboard/airdrop.css index 5d28e4e6f..2dfd2e5f7 100644 --- a/web/app.fluidity.money/app/styles/dashboard/airdrop.css +++ b/web/app.fluidity.money/app/styles/dashboard/airdrop.css @@ -962,6 +962,11 @@ text-align: center; } +.recap-fly-election-message { + font-size: 14px; + text-align: center; +} + .recap-fly-count-buttons-spread { display: flex; flex-direction: row; diff --git a/web/app.fluidity.money/app/styles/dashboard/airdrop.scss b/web/app.fluidity.money/app/styles/dashboard/airdrop.scss index 619fa1333..13f6fa4f3 100644 --- a/web/app.fluidity.money/app/styles/dashboard/airdrop.scss +++ b/web/app.fluidity.money/app/styles/dashboard/airdrop.scss @@ -1100,6 +1100,11 @@ $holo: linear-gradient( text-align: center; } +.recap-fly-election-message { + font-size: 14px; + text-align: center; +} + .recap-fly-count-buttons-spread { display: flex; flex-direction: row; diff --git a/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts b/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts index a2d718bdd..0cb71bf20 100644 --- a/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts +++ b/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts @@ -119,7 +119,7 @@ export const getUsdAmountMinted = async ( return Number(utils.formatUnits(amount, decimals)); }; -const makeContractSwap = async ( +export const makeContractSwap = async ( signer: Signer, from: ContractToken, to: ContractToken, @@ -998,4 +998,29 @@ export const handleContractErrors = async ( } }; -export default makeContractSwap; +export enum AirdropElection { + Claim = 0, + Stake, + ConvertToSpn +}; + +export const signAirdropElection_ = async ( + signer: Signer, + option: AirdropElection +): Promise => { + const address = await signer.getAddress(); + var optionStr = ""; + switch (option) { + case AirdropElection.Claim: + optionStr = "claim"; + break; + case AirdropElection.Stake: + optionStr = "stake"; + break; + case AirdropElection.ConvertToSpn: + optionStr = "convert"; + break; + } + const message = `I elect to ${optionStr} my FLY token.`; + return await signer.signMessage(utils.arrayify(utils.toUtf8Bytes(message))); +}; diff --git a/web/app.fluidity.money/contexts/EthereumProvider.tsx b/web/app.fluidity.money/contexts/EthereumProvider.tsx index 512b81848..36b44d3a8 100644 --- a/web/app.fluidity.money/contexts/EthereumProvider.tsx +++ b/web/app.fluidity.money/contexts/EthereumProvider.tsx @@ -1,6 +1,8 @@ import type { ReactNode } from "react"; import { confirmAccountOwnership_, + AirdropElection, + signAirdropElection_, signOwnerAddress_, StakingDepositsRes, } from "~/util/chainUtils/ethereum/transaction"; @@ -39,7 +41,8 @@ import { flyStakingFinaliseUnstake as doFlyStakingFinaliseUnstake, flyStakingSecondsUntilSoonestUnstake as doFlyStakingSecondsUntilSoonestUnstake, } from "~/util/chainUtils/ethereum/transaction"; -import makeContractSwap, { +import { + makeContractSwap, ContractToken, getBalanceOfERC20, } from "~/util/chainUtils/ethereum/transaction"; @@ -604,6 +607,16 @@ const EthereumFacade = ({ console.log(result); }; + const signAirdropElection = async (option: AirdropElection): Promise => { + const signer = provider?.getSigner(); + + if (!signer) { + return undefined; + } + + return signAirdropElection_(signer, option); + }; + const merkleDistributorWithDeadlineEndTime = () => { throw new Error("TODO"); }; @@ -808,6 +821,7 @@ const EthereumFacade = ({ getStakingRatios, signOwnerAddress, confirmAccountOwnership, + signAirdropElection, merkleDistributorWithDeadlineEndTime, merkleDistributorWithDeadlineClaim, merkleDistributorWithDeadlineClaimAndStake, diff --git a/web/app.fluidity.money/contexts/FluidityFacade.tsx b/web/app.fluidity.money/contexts/FluidityFacade.tsx index 242940977..b62a9122c 100644 --- a/web/app.fluidity.money/contexts/FluidityFacade.tsx +++ b/web/app.fluidity.money/contexts/FluidityFacade.tsx @@ -4,6 +4,7 @@ import type { StakingDepositsRes, StakingRedeemableRes, FLYStakingDetailsRes, + AirdropElection, } from "~/util/chainUtils/ethereum/transaction"; import type BN from "bn.js"; @@ -86,6 +87,8 @@ export interface IFluidityFacade { address: string ) => Promise; + signAirdropElection?: (option: AirdropElection) => Promise; + merkleDistributorWithDeadlineEndTime?: () => Promise; merkleDistributorWithDeadlineClaim?: (