Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MEP 10/07/2024 PAYMASTER #851

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions components/UI/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import styles from "../../styles/components/navbar.module.css";
import connectStyles from "../../styles/components/walletConnect.module.css";
import Button from "./button";
import { useConnect, useAccount, useDisconnect } from "@starknet-react/core";
import { sepolia, mainnet } from "@starknet-react/chains";
import ModalMessage from "./modalMessage";
import { useDisplayName } from "../../hooks/displayName.tsx";
import { useMediaQuery } from "@mui/material";
import { CircularProgress } from "@mui/material";
import ModalWallet from "./modalWallet";
import { constants } from "starknet";
import { useTheme } from "@mui/material/styles";
import ProfilFilledIcon from "./iconsComponents/icons/profilFilledIcon";
import DesktopNav from "./desktopNav";
Expand All @@ -32,6 +32,7 @@ import {
import WalletConnect from "./walletConnect";
import ArrowDownIcon from "./iconsComponents/icons/arrowDownIcon";
import errorLottie from "../../public/visuals/errorLottie.json";
import { bigintToStringHex } from "@/utils/stringService";

const Navbar: FunctionComponent = () => {
const theme = useTheme();
Expand All @@ -52,8 +53,8 @@ const Navbar: FunctionComponent = () => {
const { starknetIdNavigator } = useContext(StarknetIdJsContext);
const [showWalletConnectModal, setShowWalletConnectModal] =
useState<boolean>(false);
const [lastConnector, setLastConnector] = useState<Connector | null>(null);

const [lastConnector, setLastConnector] = useState<Connector | null>(null);
// could be replaced by a useProfileData from starknet-react when updated
useEffect(() => {
if (starknetIdNavigator !== null && address !== undefined) {
Expand Down Expand Up @@ -92,10 +93,8 @@ const Navbar: FunctionComponent = () => {
if (!isConnected || !account) return;
account.getChainId().then((chainId) => {
const isWrongNetwork =
(chainId === constants.StarknetChainId.SN_SEPOLIA &&
network === "mainnet") ||
(chainId === constants.StarknetChainId.SN_MAIN &&
network === "testnet");
(chainId === bigintToStringHex(sepolia.id) && network === "mainnet") ||
(chainId === bigintToStringHex(mainnet.id) && network === "testnet");
setIsWrongNetwork(isWrongNetwork);
});
}, [account, network, isConnected]);
Expand Down
76 changes: 58 additions & 18 deletions components/discount/freeRegisterCheckout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from "react";
import type { FunctionComponent } from "react";
import { useEffect, useState } from "react";
import type { Call } from "starknet";
import Button from "../UI/button";
import { useAccount, useContractWrite } from "@starknet-react/core";
import { utils } from "starknetid.js";
import { getDomainWithStark } from "../../utils/stringService";
import { posthog } from "posthog-js";
Expand All @@ -20,6 +18,9 @@ import { getFreeDomain } from "@/utils/campaignService";
import TermCheckbox from "../domains/termCheckbox";
import { useRouter } from "next/router";
import FreeRegisterSummary from "./freeRegisterSummary";
import { useAccount } from "@starknet-react/core";
import { Call } from "starknet";
import usePaymaster from "@/hooks/paymaster";

type FreeRegisterCheckoutProps = {
domain: string;
Expand Down Expand Up @@ -47,19 +48,43 @@ const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
const [termsBox, setTermsBox] = useState<boolean>(true);
const [metadataHash, setMetadataHash] = useState<string | undefined>();
const { account, address } = useAccount();
const { writeAsync: execute, data: registerData } = useContractWrite({
calls: callData,
});
const [domainsMinting, setDomainsMinting] = useState<Map<string, boolean>>(
new Map()
);
const { addTransaction } = useNotificationManager();
const router = useRouter();
const [tokenId, setTokenId] = useState<number>(0);
const [coupon, setCoupon] = useState<string>("");
const [couponError, setCouponError] = useState<string>("");
const [signature, setSignature] = useState<string[]>(["", ""]);
const [loadingCoupon, setLoadingCoupon] = useState<boolean>(false);
const { addTransaction } = useNotificationManager();
const [transactionHash, setTransactionHash] = useState<string | undefined>();
const {
handleRegister,
data: registerData,
paymasterRewards,
gasTokenPrices,
gasTokenPrice,
loadingGas,
gasMethod,
setGasMethod,
gaslessCompatibility,
setGasTokenPrice,
sponsoredDeploymentAvailable,
maxGasTokenAmount,
loadingDeploymentData,
} = usePaymaster(callData, async (transactionHash) => {
setDomainsMinting((prev) =>
new Map(prev).set(encodedDomain.toString(), true)
);
console.log(transactionHash);
if (transactionHash) setTransactionHash(transactionHash);
});

useEffect(() => {
if (!registerData?.transaction_hash) return;
setTransactionHash(registerData.transaction_hash);
}, [registerData]);

// on first load, we generate a salt
useEffect(() => {
Expand Down Expand Up @@ -99,22 +124,22 @@ const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
}

useEffect(() => {
if (!registerData?.transaction_hash) return;
if (!transactionHash) return;
posthog?.capture("register");
addTransaction({
timestamp: Date.now(),
subtext: "Domain registration",
type: NotificationType.TRANSACTION,
data: {
type: TransactionType.BUY_DOMAIN,
hash: registerData.transaction_hash,
hash: transactionHash,
status: "pending",
},
});

router.push(`/confirmation?tokenId=${tokenId}`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [registerData, tokenId]);
}, [transactionHash, tokenId]);

useEffect(() => {
if (!coupon) {
Expand Down Expand Up @@ -150,13 +175,6 @@ const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
});
}, [coupon, domain, address]);

const handleRegister = () =>
execute().then(() =>
setDomainsMinting((prev) =>
new Map(prev).set(encodedDomain.toString(), true)
)
);

return (
<div className={styles.container}>
<div className={styles.card}>
Expand All @@ -181,7 +199,21 @@ const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
</div>
</div>
<div className={styles.summary}>
<FreeRegisterSummary duration={duration} domain={domain} />
<FreeRegisterSummary
duration={duration}
domain={domain}
hasPaymasterRewards={paymasterRewards.length > 0}
gasTokenPrices={gasTokenPrices}
gasTokenPrice={gasTokenPrice}
setGasTokenPrice={setGasTokenPrice}
gasMethod={gasMethod}
setGasMethod={setGasMethod}
paymasterAvailable={
gaslessCompatibility?.isCompatible || sponsoredDeploymentAvailable
}
maxGasTokenAmount={maxGasTokenAmount}
deployed={gaslessCompatibility?.isCompatible}
/>
<Divider className="w-full" />
<TermCheckbox
checked={termsBox}
Expand All @@ -197,13 +229,21 @@ const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
!targetAddress ||
!termsBox ||
Boolean(couponError) ||
loadingCoupon
loadingCoupon ||
loadingGas ||
loadingDeploymentData
}
>
{!termsBox
? "Please accept terms & policies"
: couponError
? "Enter a valid Coupon"
: loadingGas
? "Loading gas"
: loadingDeploymentData
? paymasterRewards.length > 0
? "Loading deployment data"
: "No Paymaster reward available"
: "Register my domain"}
</Button>
) : (
Expand Down
136 changes: 136 additions & 0 deletions components/discount/freeRegisterSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import React, { FunctionComponent } from "react";
import styles from "../../styles/components/registerV3.module.css";
import { getYearlyPrice } from "@/utils/priceService";
import DoneIcon from "../UI/iconsComponents/icons/doneIcon";
import { GasTokenPrice } from "@avnu/gasless-sdk";
import { tokenNames } from "@/utils/altcoinService";
import { shortenDomain } from "@/utils/stringService";
import StyledToolTip from "../UI/styledTooltip";
import { GasMethod } from "@/hooks/paymaster";
import { Alert } from "@mui/material";

type FreeRegisterSummaryProps = {
duration: number;
domain: string;
hasPaymasterRewards?: boolean;
gasTokenPrices?: GasTokenPrice[];
gasTokenPrice?: GasTokenPrice;
setGasTokenPrice: (price: GasTokenPrice) => void;
gasMethod: GasMethod;
setGasMethod: (method: GasMethod) => void;
paymasterAvailable: boolean;
maxGasTokenAmount?: bigint;
deployed?: boolean;
};

const FreeRegisterSummary: FunctionComponent<FreeRegisterSummaryProps> = ({
domain,
duration,
hasPaymasterRewards,
gasTokenPrices,
gasTokenPrice,
setGasTokenPrice,
gasMethod,
setGasMethod,
paymasterAvailable,
maxGasTokenAmount,
deployed,
}) => {
function getMessage() {
return `${Math.floor(duration / 30)} months of domain registration`;
}

const getTokenName = (price: GasTokenPrice) =>
tokenNames[price.tokenAddress as keyof typeof tokenNames] ||
shortenDomain(price.tokenAddress);

return (
<div className={styles.pricesSummary}>
<div className={styles.totalDue}>
Expand All @@ -27,6 +56,113 @@ const FreeRegisterSummary: FunctionComponent<FreeRegisterSummaryProps> = ({
<strong>Free</strong>
</p>
</div>
<div className={styles.gasMethods}>
<button
disabled={gasMethod === "traditional"}
onClick={() => setGasMethod("traditional")}
className={
gasMethod === "traditional"
? styles.gasMethodSelected
: styles.gasMethod
}
type="button"
>
Traditional Transaction
</button>
<StyledToolTip
title={`Allows you to pay less gas and choose other currencies to pay fees. ${
paymasterAvailable
? ""
: "Wallet not compatible. Please deploy it or switch to ArgentX in order to use Paymaster."
}`}
>
<button
onClick={() => setGasMethod("paymaster")}
className={
gasMethod === "paymaster"
? styles.gasMethodSelected
: styles.gasMethod
}
type="button"
disabled={!paymasterAvailable}
>
Gasless Transaction
</button>
</StyledToolTip>
</div>
{gasMethod === "paymaster" ? (
hasPaymasterRewards ? (
<div className="flex items-center gap-2">
<DoneIcon width="24" color="green" />
<p className="text-sm">
No gas fees to pay. You have a{" "}
<a
href="https://doc.avnu.fi/starknet-paymaster/introduction"
target="_blank"
rel="noreferrer noopener"
className="underline"
>
Paymaster
</a>{" "}
reward.
</p>
</div>
) : (
<div className="flex flex-col gap-2 w-full">
<p className="text-sm">
No{" "}
<a
href="https://doc.avnu.fi/starknet-paymaster/introduction"
target="_blank"
rel="noreferrer noopener"
className="underline"
>
Paymaster
</a>{" "}
reward. {deployed ? "Please select a gas token." : ""}
</p>
{deployed ? (
<>
<div className={styles.gasMethods}>
{gasTokenPrices?.map((price) => (
<button
disabled={
price.tokenAddress === gasTokenPrice?.tokenAddress
}
onClick={() => setGasTokenPrice(price)}
key={price.tokenAddress}
className={
price.tokenAddress === gasTokenPrice?.tokenAddress
? styles.gasMethodSelected
: styles.gasMethod
}
type="button"
>
{getTokenName(price)}{" "}
</button>
))}
</div>
{gasTokenPrice ? (
<Alert severity="info">
{maxGasTokenAmount
? `Please make sure to have at least ${maxGasTokenAmount.toString()} ${getTokenName(
gasTokenPrice
)} to prevent transaction failure.`
: `Please make sure to have enough ${getTokenName(
gasTokenPrice
)} to prevent transaction failure.`}
</Alert>
) : null}
</>
) : (
<Alert severity="error">
Your wallet is not deployed. To sponsor its deployment, please
gather rewards.
</Alert>
)}
</div>
)
) : null}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ import { useAccount } from "@starknet-react/core";
import { Call, TypedData, constants } from "starknet";
import { useContractWrite } from "@starknet-react/core";
import { hexToDecimal } from "../../../../utils/feltService";
import { minifyDomain } from "../../../../utils/stringService";
import {
bigintToStringHex,
minifyDomain,
} from "../../../../utils/stringService";
import VerifiedIcon from "../../../UI/iconsComponents/icons/verifiedIcon";
import theme from "../../../../styles/theme";
import { posthog } from "posthog-js";
import ProfilSecurityIcon from "../../../UI/iconsComponents/icons/profilSecurityIcon";
import identityChangeCalls from "../../../../utils/callData/identityChangeCalls";
import { useNotificationManager } from "../../../../hooks/useNotificationManager";
import { NotificationType, TransactionType } from "../../../../utils/constants";
import { mainnet, sepolia } from "@starknet-react/chains";

type ClickablePersonhoodIconProps = {
width: string;
Expand Down Expand Up @@ -221,8 +225,8 @@ const ClickablePersonhoodIcon: FunctionComponent<
walletAddress={address}
starknetChainId={
network === "testnet"
? constants.StarknetChainId.SN_SEPOLIA
: constants.StarknetChainId.SN_MAIN
? bigintToStringHex(sepolia.id)
: bigintToStringHex(mainnet.id)
}
chainType="STARKNET"
/>
Expand Down
Loading