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

ref: free renewal adaptation #876

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
160 changes: 41 additions & 119 deletions components/discount/freeRenewalCheckout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ import React from "react";
import { FunctionComponent, useEffect, useState } from "react";
import Button from "../UI/button";
import { useAccount, useContractWrite } from "@starknet-react/core";
import {
formatHexString,
isValidEmail,
selectedDomainsToArray,
selectedDomainsToEncodedArray,
} from "../../utils/stringService";
import { formatHexString, isValidEmail } from "../../utils/stringService";
import { applyRateToBigInt } from "../../utils/feltService";
import { Call } from "starknet";
import styles from "../../styles/components/registerV2.module.css";
import TextField from "../UI/textField";
import SwissForm from "../domains/swissForm";
Expand All @@ -18,36 +12,25 @@ import RegisterSummary from "../domains/registerSummary";
import { computeMetadataHash, generateSalt } from "../../utils/userDataService";
import {
areDomainSelected,
getApprovalAmount,
getDisplayablePrice,
} from "../../utils/priceService";
import RenewalDomainsBox from "../domains/renewalDomainsBox";
import registrationCalls from "../../utils/callData/registrationCalls";
import autoRenewalCalls from "../../utils/callData/autoRenewalCalls";
import BackButton from "../UI/backButton";
import { useRouter } from "next/router";
import { useNotificationManager } from "../../hooks/useNotificationManager";
import {
AutoRenewalContracts,
CurrencyType,
ERC20Contract,
NotificationType,
TransactionType,
swissVatRate,
} from "../../utils/constants";
import RegisterCheckboxes from "../domains/registerCheckboxes";
import { utils } from "starknetid.js";
import RegisterConfirmationModal from "../UI/registerConfirmationModal";
import useNeedsAllowances from "../../hooks/useNeedAllowances";
import ConnectButton from "../UI/connectButton";
import {
getAutoRenewAllowance,
getDomainPrice,
getTokenQuote,
getTotalYearlyPrice,
} from "../../utils/altcoinService";
import { getTokenQuote, getTotalYearlyPrice } from "../../utils/altcoinService";
import { areArraysEqual } from "@/utils/arrayService";
import useNeedSubscription from "@/hooks/useNeedSubscription";
import { useFreeRenewalTxPrep } from "@/hooks/checkout/useFreeRenewalTxPrep";

type FreeRenewalCheckoutProps = {
groups: string[];
Expand All @@ -65,7 +48,6 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
const [isSwissResident, setIsSwissResident] = useState<boolean>(false);
const [salesTaxRate, setSalesTaxRate] = useState<number>(0);
const [salesTaxAmount, setSalesTaxAmount] = useState<bigint>(BigInt(0));
const [callData, setCallData] = useState<Call[]>([]);
// price paid by the user including discount
const [quoteData, setQuoteData] = useState<QuoteQueryData | null>(null); // null if in ETH
const [displayedCurrencies, setDisplayedCurrencies] = useState<
Expand All @@ -76,13 +58,28 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
const [salt, setSalt] = useState<string | undefined>();
const [metadataHash, setMetadataHash] = useState<string | undefined>();
const [needMetadata, setNeedMetadata] = useState<boolean>(false);
const [potentialPrice, setPotentialPrice] = useState<bigint>(BigInt(0));
const [potentialPrices, setPotentialPrices] = useState<
Record<CurrencyType, bigint>
>({
[CurrencyType.ETH]: BigInt(0),
[CurrencyType.STRK]: BigInt(0),
});
const [customCheckoutMessage, setCustomCheckoutMessage] =
useState<string>("");

const [selectedDomains, setSelectedDomains] =
useState<Record<string, boolean>>();
const { address } = useAccount();
const { callData } = useFreeRenewalTxPrep(
quoteData,
salesTaxAmount,
displayedCurrencies,
salesTaxRate,
potentialPrices,
selectedDomains,
address,
metadataHash
);
const { writeAsync: execute, data: renewData } = useContractWrite({
calls: callData,
});
Expand All @@ -91,8 +88,6 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
const { addTransaction } = useNotificationManager();
const router = useRouter();
const [loadingPrice, setLoadingPrice] = useState<boolean>(false);
const allowanceStatus = useNeedsAllowances(address);
const needSubscription = useNeedSubscription(address);

useEffect(() => {
if (!renewData?.transaction_hash || !salt || !metadataHash) return;
Expand Down Expand Up @@ -234,95 +229,14 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
useEffect(() => {
if (isSwissResident) {
setSalesTaxRate(swissVatRate);
setSalesTaxAmount(applyRateToBigInt(potentialPrice, swissVatRate));
setSalesTaxAmount(
applyRateToBigInt(potentialPrices[CurrencyType.ETH], swissVatRate)
);
} else {
setSalesTaxRate(0);
setSalesTaxAmount(BigInt(0));
}
}, [isSwissResident, potentialPrice]);

// build free renewal call
useEffect(() => {
const isCurrencyETH = areArraysEqual(displayedCurrencies, [
CurrencyType.ETH,
]);

if (!isCurrencyETH && !quoteData) return;
const txMetadataHash = `0x${metadataHash}` as HexString;
if (selectedDomains) {
// Free renewal
const calls = [
...registrationCalls.multiCallFreeRenewals(
selectedDomainsToEncodedArray(selectedDomains),
AutoRenewalContracts[displayedCurrencies[0]] // If we have multiple currencies, we use the first one (which is ETH)
),
];

displayedCurrencies.map((currency) => {
// Add ERC20 allowance for all currencies if needed
if (allowanceStatus[currency].needsAllowance) {
const amountToApprove = getApprovalAmount(
potentialPrice,
salesTaxAmount,
1,
allowanceStatus[currency].currentAllowance
);

calls.unshift(
autoRenewalCalls.approve(
ERC20Contract[currency],
AutoRenewalContracts[currency],
amountToApprove
)
);
}
// Add AutoRenewal calls for all currencies
selectedDomainsToArray(selectedDomains).map((domain) => {
if (
needSubscription &&
needSubscription.needSubscription[domain]?.[currency]
) {
const encodedDomain = utils
.encodeDomain(domain)
.map((element) => element.toString())[0];

const domainPrice = getDomainPrice(
domain,
currency,
365,
quoteData?.quote
);
const allowance = getAutoRenewAllowance(
currency,
salesTaxRate,
domainPrice
);

calls.unshift(
autoRenewalCalls.enableRenewal(
AutoRenewalContracts[currency],
encodedDomain,
allowance, // Warning review: Do we need to multiply by 10 here to have 10 years ?
txMetadataHash
)
);
}
});
});
setCallData(calls);
}
}, [
selectedDomains,
salesTaxAmount,
allowanceStatus,
metadataHash,
salesTaxRate,
needMetadata,
quoteData,
displayedCurrencies,
needSubscription,
potentialPrice,
]);
}, [isSwissResident, potentialPrices]);

useEffect(() => {
const isCurrencySTRK = areArraysEqual(displayedCurrencies, [
Expand All @@ -331,20 +245,28 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
const currencyDisplayed = isCurrencySTRK
? CurrencyType.STRK
: CurrencyType.ETH;
const totalYearlyPrice = getTotalYearlyPrice(
selectedDomains,
currencyDisplayed,
quoteData?.quote
);

const newPotentialPrices = displayedCurrencies.reduce((acc, currency) => {
const totalYearlyPrice = getTotalYearlyPrice(
selectedDomains,
currency,
quoteData?.quote
);
acc[currency] = totalYearlyPrice;
return acc;
}, {} as Record<CurrencyType, bigint>);

setPotentialPrices((prevPrices) => ({
...prevPrices,
...newPotentialPrices,
}));

const potentialCurrency = quoteData?.quote
? currencyDisplayed
: CurrencyType.ETH;

// Setting the potential price in the displayed currency
setPotentialPrice(totalYearlyPrice);
setCustomCheckoutMessage(
`${offer.customMessage} and then ${getDisplayablePrice(
totalYearlyPrice
newPotentialPrices[potentialCurrency] ?? BigInt(0)
)} ${potentialCurrency} per year`
);

Expand Down Expand Up @@ -395,7 +317,7 @@ const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
</div>
<div className={styles.summary}>
<RegisterSummary
priceInEth={potentialPrice}
priceInEth={potentialPrices[CurrencyType.ETH]}
price={offer.price}
durationInYears={offer.durationInDays / 365}
salesTaxRate={salesTaxRate}
Expand Down
123 changes: 123 additions & 0 deletions hooks/checkout/useFreeRenewalTxPrep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import {
AutoRenewalContracts,
CurrencyType,
ERC20Contract,
} from "@/utils/constants";
import { Call } from "starknet";
import registrationCalls from "@/utils/callData/registrationCalls";
import { utils } from "starknetid.js";
import { getAutoRenewAllowance, getDomainPrice } from "@/utils/altcoinService";
import { getApprovalAmount } from "@/utils/priceService";
import autoRenewalCalls from "@/utils/callData/autoRenewalCalls";
import {
selectedDomainsToArray,
selectedDomainsToEncodedArray,
} from "@/utils/stringService";
import useNeedsAllowances from "../useNeedAllowances";
import useNeedSubscription from "../useNeedSubscription";
import { areArraysEqual } from "@/utils/arrayService";
import { isEqual } from "lodash";

export const useFreeRenewalTxPrep = (
quoteData: QuoteQueryData | null,
salesTaxAmount: bigint,
displayedCurrencies: CurrencyType[],
salesTaxRate: number,
potentialPrices: Record<CurrencyType, bigint>,
selectedDomains?: Record<string, boolean>,
address?: string,
metadataHash?: string
) => {
const [callData, setCallData] = useState<Call[]>([]);
const allowanceStatus = useNeedsAllowances(address);
const needSubscription = useNeedSubscription(address);

const isCurrencyETH = useMemo(
() => areArraysEqual(displayedCurrencies, [CurrencyType.ETH]),
[displayedCurrencies]
);

const buildCalls = useCallback(() => {
if (!isCurrencyETH && !quoteData) return [];
const txMetadataHash = `0x${metadataHash}` as HexString;
if (!selectedDomains) return [];

const calls: Call[] = [
...registrationCalls.multiCallFreeRenewals(
selectedDomainsToEncodedArray(selectedDomains),
AutoRenewalContracts[displayedCurrencies[0]]
),
];

displayedCurrencies.forEach((currency) => {
if (allowanceStatus[currency].needsAllowance) {
const amountToApprove = getApprovalAmount(
potentialPrices[currency],
salesTaxAmount,
1,
allowanceStatus[currency].currentAllowance
);

calls.unshift(
autoRenewalCalls.approve(
ERC20Contract[currency],
AutoRenewalContracts[currency],
amountToApprove
)
);
}

selectedDomainsToArray(selectedDomains).forEach((domain) => {
if (needSubscription.needSubscription[domain]?.[currency]) {
const encodedDomain = utils
.encodeDomain(domain)
.map((element) => element.toString())[0];

const domainPrice = getDomainPrice(
domain,
currency,
365,
quoteData?.quote
);
const allowance = getAutoRenewAllowance(
currency,
salesTaxRate,
domainPrice
);

calls.unshift(
autoRenewalCalls.enableRenewal(
AutoRenewalContracts[currency],
encodedDomain,
allowance,
txMetadataHash
)
);
}
});
});

return calls;
}, [
fricoben marked this conversation as resolved.
Show resolved Hide resolved
isCurrencyETH,
quoteData,
selectedDomains,
displayedCurrencies,
allowanceStatus,
potentialPrices,
salesTaxAmount,
needSubscription,
salesTaxRate,
metadataHash,
]);

useEffect(() => {
const newCallData = buildCalls();
if (!isEqual(newCallData, callData)) {
setCallData(newCallData);
}
}, [buildCalls, callData]);

return { callData };
};
fricoben marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 2 additions & 3 deletions utils/apiWrappers/identity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { shortString } from "starknet";
import { fromUint256, hexToDecimal } from "../feltService";
import { formatHexString, getImgUrl } from "../stringService";
import {
Expand Down Expand Up @@ -84,15 +83,15 @@ export class Identity {
}

get targetAddress(): string {
let legacyAddress = this._data.domain?.legacy_address;
const legacyAddress = this._data.domain?.legacy_address;
if (
legacyAddress &&
legacyAddress !==
"0x0000000000000000000000000000000000000000000000000000000000000000"
) {
return legacyAddress;
}
let starknetFromId = this.getUserData(STARKNET);
const starknetFromId = this.getUserData(STARKNET);
if (
starknetFromId &&
starknetFromId !==
Expand Down
Loading