Skip to content
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
130 changes: 66 additions & 64 deletions ui/hooks/subscription/useSubscriptionPricing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import log from 'loglevel';
import {
Expand Down Expand Up @@ -43,7 +43,11 @@ export const useAvailableTokenBalances = (params: {
paymentChains?: ChainPaymentInfo[];
price?: ProductPrice;
productType: ProductType;
}): TokenWithApprovalAmount[] => {
}): {
availableTokenBalances: TokenWithApprovalAmount[];
pending: boolean;
error: Error | undefined;
} => {
const { paymentChains, price, productType } = params;

const paymentChainIds = useMemo(
Expand All @@ -70,10 +74,6 @@ export const useAvailableTokenBalances = (params: {
// Poll and update evm balances for payment chains
pollAndUpdateEvmBalances({ chainIds: paymentChainIds });

const [availableTokenBalances, setAvailableTokenBalances] = useState<
TokenWithApprovalAmount[]
>([]);

const validTokenBalances = useMemo(() => {
return evmBalances.filter((token) => {
const supportedTokensForChain =
Expand All @@ -96,72 +96,74 @@ export const useAvailableTokenBalances = (params: {
});
}, [evmBalances, paymentChainTokenMap]);

useEffect(() => {
const {
value: availableTokenBalances,
pending,
error,
} = useAsyncResult(async (): Promise<TokenWithApprovalAmount[]> => {
if (!price || !paymentChainTokenMap) {
return;
return [];
}

const getAvailableTokenBalances = async () => {
const availableTokens: TokenWithApprovalAmount[] = [];

const cryptoApprovalAmounts = await Promise.all(
validTokenBalances.map((token) => {
const tokenPaymentInfo = paymentChainTokenMap?.[
token.chainId as Hex
]?.find(
(t) => t.address.toLowerCase() === token.address.toLowerCase(),
const availableTokens: TokenWithApprovalAmount[] = [];

const cryptoApprovalAmounts = await Promise.all(
validTokenBalances.map((token) => {
const tokenPaymentInfo = paymentChainTokenMap?.[
token.chainId as Hex
]?.find((t) => t.address.toLowerCase() === token.address.toLowerCase());
if (!tokenPaymentInfo) {
log.error(
'[useAvailableTokenBalances] tokenPaymentInfo not found',
token,
);
if (!tokenPaymentInfo) {
log.error(
'[useAvailableTokenBalances] tokenPaymentInfo not found',
token,
);
return null;
}
return getSubscriptionCryptoApprovalAmount({
chainId: token.chainId as Hex,
paymentTokenAddress: token.address as Hex,
productType,
interval: price.interval,
});
}),
);

cryptoApprovalAmounts.forEach((amount, index) => {
const token = validTokenBalances[index];
if (!token.balance) {
return;
}
// NOTE: we are using stable coin for subscription atm, so we need to scale the balance by the decimals
const scaledFactor = 10n ** 6n;
const scaledBalance =
BigInt(Math.round(Number(token.balance) * Number(scaledFactor))) /
scaledFactor;
const tokenHasEnoughBalance =
amount &&
scaledBalance * BigInt(10 ** token.decimals) >=
BigInt(amount.approveAmount);
if (tokenHasEnoughBalance) {
availableTokens.push({
...token,
approvalAmount: {
approveAmount: amount.approveAmount,
chainId: token.chainId as Hex,
paymentAddress: amount.paymentAddress,
paymentTokenAddress: amount.paymentTokenAddress,
},
type: token.isNative ? AssetType.native : AssetType.token,
} as TokenWithApprovalAmount);
return null;
}
});

setAvailableTokenBalances(availableTokens);
};
return getSubscriptionCryptoApprovalAmount({
chainId: token.chainId as Hex,
paymentTokenAddress: token.address as Hex,
productType,
interval: price.interval,
});
}),
);

cryptoApprovalAmounts.forEach((amount, index) => {
const token = validTokenBalances[index];
if (!token.balance) {
return;
}
// NOTE: we are using stable coin for subscription atm, so we need to scale the balance by the decimals
const scaledFactor = 10n ** 6n;
const scaledBalance =
BigInt(Math.round(Number(token.balance) * Number(scaledFactor))) /
scaledFactor;
const tokenHasEnoughBalance =
amount &&
scaledBalance * BigInt(10 ** token.decimals) >=
BigInt(amount.approveAmount);
if (tokenHasEnoughBalance) {
availableTokens.push({
...token,
approvalAmount: {
approveAmount: amount.approveAmount,
chainId: token.chainId as Hex,
paymentAddress: amount.paymentAddress,
paymentTokenAddress: amount.paymentTokenAddress,
},
type: token.isNative ? AssetType.native : AssetType.token,
} as TokenWithApprovalAmount);
}
});

getAvailableTokenBalances();
return availableTokens;
}, [price, productType, paymentChainTokenMap, validTokenBalances]);

return availableTokenBalances;
return {
availableTokenBalances: availableTokenBalances ?? [],
pending,
error,
};
};

/**
Expand Down
28 changes: 21 additions & 7 deletions ui/pages/shield-plan/shield-plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,12 @@ const ShieldPlan = () => {
return pricingPlans?.find((plan) => plan.interval === selectedPlan);
}, [pricingPlans, selectedPlan]);

const availableTokenBalances = useAvailableTokenBalances({
paymentChains: cryptoPaymentMethod?.chains,
price: selectedProductPrice,
productType: PRODUCT_TYPES.SHIELD,
});
const { availableTokenBalances, pending: pendingAvailableTokenBalances } =
useAvailableTokenBalances({
paymentChains: cryptoPaymentMethod?.chains,
price: selectedProductPrice,
productType: PRODUCT_TYPES.SHIELD,
});
const hasAvailableToken = availableTokenBalances.length > 0;

const [selectedPaymentMethod, setSelectedPaymentMethod] =
Expand All @@ -165,17 +166,23 @@ const ShieldPlan = () => {

// set selected token to the first available token if no token is selected
useEffect(() => {
if (selectedToken || availableTokenBalances.length === 0) {
if (
pendingAvailableTokenBalances ||
selectedToken ||
availableTokenBalances.length === 0
) {
return;
}

const lastUsedPaymentToken = lastUsedPaymentDetails?.paymentTokenAddress;
const lastUsedPaymentMethod = lastUsedPaymentDetails?.type;
const lastUsedPaymentPlan = lastUsedPaymentDetails?.plan;

let lastUsedSelectedToken = availableTokenBalances[0];
if (
lastUsedPaymentToken &&
lastUsedPaymentMethod === PAYMENT_TYPES.byCrypto
lastUsedPaymentMethod === PAYMENT_TYPES.byCrypto &&
lastUsedPaymentPlan === selectedPlan
) {
lastUsedSelectedToken =
availableTokenBalances.find(
Expand All @@ -185,12 +192,19 @@ const ShieldPlan = () => {

setSelectedToken(lastUsedSelectedToken);
}, [
pendingAvailableTokenBalances,
availableTokenBalances,
selectedToken,
setSelectedToken,
lastUsedPaymentDetails,
selectedPlan,
]);

// reset selected token if selected plan changes
useEffect(() => {
setSelectedToken(undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this set to be default instead of undefined?
Like what we do in the above useEffects.
If I'm not mistaken, we don't need new side effects hook for this (we already have the (useEffect) hook which does the similar thing)?

Copy link
Member Author

Choose a reason for hiding this comment

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

ah no that wouldn't work, since the above effect is only set if there is no selected token set yet to set the default token, if there is already token set, it won't trigger again, so that it won't run everytime token balance change

}, [selectedPlan, setSelectedToken]);

// set default selected payment method to crypto if selected token available
useEffect(() => {
if (selectedToken) {
Expand Down
Loading