Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/ID-2758-balances-ro…
Browse files Browse the repository at this point in the history
…utes' into ID-2758-balances-routes
  • Loading branch information
alejoloaiza committed Dec 18, 2024
2 parents b13c724 + 17a9258 commit 7ffbebb
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 123 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ jobs:
tag: ${{ contains(env.RELEASE_TYPE, 'alpha') && 'alpha' }}
dry-run: ${{ env.DRY_RUN }}

- name: Generate last_updated.json
if: (env.DRY_RUN) == 'false'
run: |
echo "{\"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}" > ./sdk/last_updated.json
cp ./sdk/last_updated.json ./sdk/dist/
# ! Do NOT remove - this will cause a Sev 0 incident !
- name: Generate SDK attestation
uses: actions/attest-build-provenance@v1
Expand Down
68 changes: 38 additions & 30 deletions packages/checkout/sdk/src/widgets/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,33 +84,6 @@ export async function getLatestVersionFromNpm(): Promise<string> {
}
}

/**
* Checks if the provided version is available on the CDN.
* @param {string} version - The version to check.
* @returns {Promise<boolean>} A promise resolving to a boolean indicating if the version is available on the CDN.
*/
async function isVersionAvailableOnCDN(version: string): Promise<boolean> {
const files = ['widgets-esm.js', 'widgets.js'];
const baseUrl = `https://cdn.jsdelivr.net/npm/@imtbl/sdk@${version}/dist/browser/checkout/`;

try {
const checks = files.map(async (file) => {
const response = await fetch(`${baseUrl}${file}`, { method: 'HEAD' });
if (!response.ok) {
return false;
}
return true;
});

const results = await Promise.all(checks);
const allFilesAvailable = results.every((isAvailable) => isAvailable);

return allFilesAvailable;
} catch {
return false;
}
}

/**
* Returns the latest compatible version based on the provided checkout version config.
* If no compatible version markers are provided, it returns 'latest'.
Expand All @@ -127,6 +100,40 @@ function latestCompatibleVersion(
return 'latest';
}

/**
* Checks if the last_updated.json file exists on the CDN and validates its timestamp.
* @param {string} version - The version to check.
* @returns {Promise<boolean>} A promise resolving to `true` if last_updated.json exists and is older than 15 minutes, `false` otherwise.
*/
async function checkLastUpdatedTimestamp(version: string): Promise<boolean> {
const WAIT_TIME_IN_MINUTES = 45;

const lastUpdatedJsonUrl = `https://cdn.jsdelivr.net/npm/@imtbl/sdk@${version}/dist/last_updated.json`;

try {
const response = await fetch(lastUpdatedJsonUrl);

if (!response.ok) {
return false;
}

const lastUpdatedData = await response.json();

if (lastUpdatedData.timestamp) {
const timestamp = new Date(lastUpdatedData.timestamp);
const now = new Date();
const diffInMs = now.getTime() - timestamp.getTime();
const diffInMinutes = diffInMs / (1000 * 60);

return diffInMinutes > WAIT_TIME_IN_MINUTES;
}
} catch (error) {
return false;
}

return false;
}

/**
* Determines the version of the widgets to use based on the provided validated build version and checkout version config.
* If a version is provided in the widget init parameters, it uses that version.
Expand Down Expand Up @@ -158,13 +165,14 @@ export async function determineWidgetsVersion(
versionConfig.compatibleVersionMarkers,
);

// If `latest` is returned, query NPM registry for the actual latest version and check if it's available on the CDN
// If `latest` is returned, query NPM registry for the actual latest version and check timestamp
if (compatibleVersion === 'latest') {
const latestVersion = await getLatestVersionFromNpm();
const isAvailable = await isVersionAvailableOnCDN(latestVersion);
if (isAvailable) {

if (await checkLastUpdatedTimestamp(latestVersion)) {
return latestVersion;
}

return 'latest';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export interface RouteOptionProps<
rc?: RC;
selected?: boolean;
displayPriceDetails?: boolean;
displayInsufficientGasWarning?: boolean;
}

export function RouteOption<RC extends ReactElement | undefined = undefined>({
Expand All @@ -42,7 +41,6 @@ export function RouteOption<RC extends ReactElement | undefined = undefined>({
rc = <span />,
selected = false,
displayPriceDetails = true,
displayInsufficientGasWarning = true,
}: RouteOptionProps<RC>) {
const { t } = useTranslation();

Expand Down Expand Up @@ -101,7 +99,7 @@ export function RouteOption<RC extends ReactElement | undefined = undefined>({

<MenuItem.Caption>
{`${t('views.ADD_TOKENS.fees.balance')} ${t('views.ADD_TOKENS.fees.fiatPricePrefix')} $${routeBalanceUsd}`}
{displayInsufficientGasWarning && routeData.isInsufficientGas && (
{ displayPriceDetails && routeData.isInsufficientGas && (
<>
<br />
<span style={{ color: '#FF637F' }}>
Expand All @@ -112,6 +110,19 @@ export function RouteOption<RC extends ReactElement | undefined = undefined>({
</span>
</>
)}

{ displayPriceDetails && routeData.isInsufficientBalance && (
<>
<br />
<span style={{ color: '#FF637F' }}>
{/* {t('views.ADD_TOKENS.noBalanceRouteMessage', {
token: fromToken.symbol,
})} */}
{' '}
Insufficient balance
</span>
</>
)}
</MenuItem.Caption>

<MenuItem.PriceDisplay price={fromAmount} sx={{ visibility: displayPriceDetails ? 'visible' : 'hidden' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface OptionsProps {
insufficientBalance?: boolean;
selectedIndex: number;
displayPriceDetails?: boolean;
displayInsufficientGasWarning?: boolean;
}

export function RouteOptions({
Expand All @@ -41,7 +40,6 @@ export function RouteOptions({
insufficientBalance,
selectedIndex,
displayPriceDetails,
displayInsufficientGasWarning,
}: OptionsProps) {
const { t } = useTranslation();

Expand Down Expand Up @@ -99,7 +97,6 @@ export function RouteOptions({
selected={index === selectedIndex}
rc={<motion.div variants={listItemVariants} />}
displayPriceDetails={displayPriceDetails}
displayInsufficientGasWarning={displayInsufficientGasWarning}
/>
))}
{noRoutes && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type OptionsDrawerProps = {
showBridgeOption?: boolean;
insufficientBalance?: boolean;
displayPriceDetails?: boolean;
displayInsufficientGasWarning?: boolean;
};

export function RouteOptionsDrawer({
Expand All @@ -43,7 +42,6 @@ export function RouteOptionsDrawer({
showBridgeOption,
insufficientBalance,
displayPriceDetails,
displayInsufficientGasWarning,
}: OptionsDrawerProps) {
const { t } = useTranslation();
const { track } = useAnalytics();
Expand Down Expand Up @@ -131,7 +129,6 @@ export function RouteOptionsDrawer({
insufficientBalance={insufficientBalance}
selectedIndex={selectedRouteIndex.current}
displayPriceDetails={displayPriceDetails}
displayInsufficientGasWarning={displayInsufficientGasWarning}
/>
</Drawer.Content>
</Drawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export interface SelectedRouteOptionProps {
insufficientBalance?: boolean;
showOnrampOption?: boolean;
displayPriceDetails?: boolean;
displayInsufficientGasWarning?: boolean;
}

function SelectedRouteOptionContainer({
Expand Down Expand Up @@ -72,9 +71,9 @@ export function SelectedRouteOption({
insufficientBalance = false,
showOnrampOption = false,
displayPriceDetails = true,
displayInsufficientGasWarning = true,
onClick,
}: SelectedRouteOptionProps) {
console.log('SelectedRouteOption', routeData);
const { t } = useTranslation();

const { fromToken } = routeData?.amountData ?? {};
Expand Down Expand Up @@ -188,14 +187,27 @@ export function SelectedRouteOption({
{`${t('views.ADD_TOKENS.fees.balance')} ${t(
'views.ADD_TOKENS.fees.fiatPricePrefix',
)} $${routeBalanceUsd}`}
{displayInsufficientGasWarning && routeData?.isInsufficientGas && (
{ displayPriceDetails && routeData?.isInsufficientGas && (
<>
<br />
<span style={{ color: '#FF637F' }}>
{t('views.ADD_TOKENS.noGasRouteMessage', {
token:
routeData.route.route.estimate.gasCosts[0].token.symbol,
})}
</span>
</>
)}

{ displayPriceDetails && routeData?.isInsufficientBalance && (
<>
<br />
<span style={{ color: '#FF637F' }}>
{t('views.ADD_TOKENS.noGasRouteMessage', {
token:
routeData.route.route.estimate.gasCosts[0].token.symbol,
})}
{/* {t('views.ADD_TOKENS.noBalanceRouteMessage', {
token: fromToken.symbol,
})} */}
{' '}
Insufficient balance
</span>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ export const sortRoutesByFastestTime = (routes: RouteData[]): RouteData[] => {
if (!routes) return [];

return routes.slice().sort((a, b) => {
// Prioritize isInsufficientGas = false
if (a.isInsufficientGas !== b.isInsufficientGas) {
return a.isInsufficientGas ? 1 : -1;
// 1.: Prioritise routes where both isInsufficientGas and isInsufficientBalance are false
if (a.isInsufficientGas !== b.isInsufficientGas || a.isInsufficientBalance !== b.isInsufficientBalance) {
if (!a.isInsufficientGas && !a.isInsufficientBalance) return -1;
if (!b.isInsufficientGas && !b.isInsufficientBalance) return 1;
if (a.isInsufficientGas && !a.isInsufficientBalance) return -1; // isInsufficientGas = true has higher priority
if (b.isInsufficientGas && !b.isInsufficientBalance) return 1;
}

// Sort by estimatedRouteDuration if isInsufficientGas is the same
// 2.: Sort by estimatedRouteDuration
const timeA = a.route.route.estimate.estimatedRouteDuration;
const timeB = b.route.route.estimate.estimatedRouteDuration;

return timeA - timeB;
if (timeA !== timeB) return timeA - timeB;

// 3.: Place isInsufficientGas before isInsufficientBalance
if (a.isInsufficientGas !== b.isInsufficientGas) {
return a.isInsufficientGas ? -1 : 1;
}

return 0;
});
};
Loading

0 comments on commit 7ffbebb

Please sign in to comment.