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

Publish Stage #3959

Merged
merged 5 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion packages/trpc/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ export const assetsRouter = createTRPCRouter({
if (
!isNil(asset.variantGroupKey) &&
!variantsNotToBeExcluded.includes(
asset.variantGroupKey as (typeof variantsNotToBeExcluded)[number]
asset.coinDenom as (typeof variantsNotToBeExcluded)[number]
)
) {
return asset.variantGroupKey === asset.coinMinimalDenom;
Expand Down
9 changes: 4 additions & 5 deletions packages/web/components/bridge/asset-select-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import { Spinner } from "~/components/loaders";
import { Tooltip } from "~/components/tooltip";
import {
MainnetAssetSymbols,
MainnetVariantGroupKeys,
TestnetAssetSymbols,
TestnetVariantGroupKeys,
} from "~/config/generated/asset-lists";
import { useTranslation, useWindowSize } from "~/hooks";
import { useKeyboardNavigation } from "~/hooks/use-keyboard-navigation";
Expand All @@ -26,9 +24,10 @@ import { UnverifiedAssetsState } from "~/stores/user-settings/unverified-assets"
import { formatPretty } from "~/utils/formatter";
import { api, RouterOutputs } from "~/utils/trpc";

const variantsNotToBeExcluded = [
"factory/osmo1z0qrq605sjgcqpylfl4aa6s90x738j7m58wyatt0tdzflg2ha26q67k743/wbtc",
] satisfies (MainnetVariantGroupKeys | TestnetVariantGroupKeys)[];
const variantsNotToBeExcluded = ["WBTC"] satisfies (
| MainnetAssetSymbols
| TestnetAssetSymbols
)[];
const prioritizedDenoms = [
"USDC",
"ETH",
Expand Down
15 changes: 10 additions & 5 deletions packages/web/components/swap-tool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
Expand Down Expand Up @@ -143,11 +144,15 @@ export const SwapTool: FunctionComponent<SwapToolProps> = observer(
}
}

const outputDifference = new RatePretty(
swapState.inAmountInput?.fiatValue
?.toDec()
.sub(swapState.tokenOutFiatValue?.toDec())
.quo(swapState.inAmountInput?.fiatValue?.toDec()) ?? new Dec(0)
const outputDifference = useMemo(
() =>
new RatePretty(
swapState.inAmountInput?.fiatValue
?.toDec()
.sub(swapState.tokenOutFiatValue?.toDec())
.quo(swapState.inAmountInput?.fiatValue?.toDec()) ?? new Dec(0)
),
[swapState.inAmountInput?.fiatValue, swapState.tokenOutFiatValue]
);

const showOutputDifferenceWarning = outputDifference
Expand Down
4 changes: 2 additions & 2 deletions packages/web/components/table/portfolio-asset-balances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ const AssetActionsCell: AssetCellComponent<{
{needsActivation && (
<Button
variant="secondary"
className="max-h-12 mx-auto w-[108px] rounded-[48px] bg-osmoverse-alpha-850 hover:bg-osmoverse-alpha-800"
className="max-h-12 rounded-[48px] bg-osmoverse-alpha-850 hover:bg-osmoverse-alpha-800"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
Expand All @@ -587,7 +587,7 @@ const AssetActionsCell: AssetCellComponent<{
{showConvertButton ? (
<Button
variant="secondary"
className="max-h-12 w-[108px] rounded-[48px] bg-osmoverse-alpha-850 hover:bg-osmoverse-alpha-800"
className="max-h-12 rounded-[48px] bg-osmoverse-alpha-850 hover:bg-osmoverse-alpha-800"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
Expand Down
26 changes: 21 additions & 5 deletions packages/web/config/generate-lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
IbcTransferMethod,
} from "@osmosis-labs/types";
import { isNil } from "@osmosis-labs/utils";
import * as fs from "fs";

import { generateTsFile } from "~/utils/codegen";

Expand All @@ -31,10 +32,12 @@ import {
OSMOSIS_CHAIN_NAME_OVERWRITE,
} from "./env";
import {
codegenDir,
getChainList,
getImageRelativeFilePath,
getOsmosisChainId,
saveAssetImageToTokensDir,
writeCurrentAssetListHash,
} from "./utils";

interface ResponseAssetList {
Expand All @@ -43,7 +46,6 @@ interface ResponseAssetList {
}

const repo = "osmosis-labs/assetlists";
const codegenDir = "config/generated";

function getFilePath({
chainId,
Expand Down Expand Up @@ -303,15 +305,18 @@ async function generateAssetListFile({

async function generateAssetImages({
assetList,
commitHash,
}: {
assetList: ResponseAssetList;
commitHash: string;
}) {
console.time("Successfully downloaded images");
for await (const asset of assetList.assets) {
await saveAssetImageToTokensDir(
asset?.logoURIs.svg ?? asset?.logoURIs.png ?? "",
asset
);
await saveAssetImageToTokensDir({
imageUrl: asset?.logoURIs.svg ?? asset?.logoURIs.png ?? "",
asset,
currentAssetListHash: commitHash,
});
}
console.timeEnd("Successfully downloaded images");
}
Expand All @@ -331,12 +336,20 @@ async function getLatestCommitHash() {
}

async function main() {
if (!fs.existsSync(codegenDir)) {
fs.mkdirSync(codegenDir);
}

const mainnetOsmosisChainId = getOsmosisChainId("mainnet");
const testnetOsmosisChainId = getOsmosisChainId("testnet");

const mainLatestCommitHash =
ASSET_LIST_COMMIT_HASH ?? (await getLatestCommitHash());

if (!mainLatestCommitHash) {
throw new Error("Failed to get latest commit hash");
}

console.info(`Using hash '${mainLatestCommitHash}' to generate assets`);

const [
Expand Down Expand Up @@ -381,8 +394,11 @@ async function main() {

await generateAssetImages({
assetList: IS_TESTNET ? testnetResponseAssetList : mainnetResponseAssetList,
commitHash: mainLatestCommitHash,
});

writeCurrentAssetListHash(mainLatestCommitHash);

let mainnetAssetLists: AssetList[] | undefined;
let testnetAssetLists: AssetList[] | undefined;

Expand Down
61 changes: 54 additions & 7 deletions packages/web/config/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,52 @@ function getNodeImageRelativeFilePath(imageUrl: string, symbol: string) {
return path.join("/public", tokensDir, `${symbol.toLowerCase()}.${fileType}`);
}

export const codegenDir = "config/generated";

// Path to the lock file
const lockFilePath = path.join(path.resolve(), `${codegenDir}/asset-lock.json`);

/**
* Read the stored asset list hash from the lock file.
* @returns The stored hash or null if the lock file doesn't exist.
*/
function readStoredAssetListHash(): string | null {
if (!fs.existsSync(lockFilePath)) {
return null;
}
const data = fs.readFileSync(lockFilePath, "utf-8");
try {
const parsed = JSON.parse(data);
return parsed.assetListHash || null;
} catch {
return null;
}
}

/**
* Write the current asset list hash to the lock file.
* @param hash The hash to store.
*/
export function writeCurrentAssetListHash(hash: string): void {
const data = { assetListHash: hash };
fs.writeFileSync(lockFilePath, JSON.stringify(data, null, 2), "utf-8");
}

/**
* Download an image from the provided URL and save it to the local file system.
* @param imageUrl The URL of the image to download.
* @returns The filename of the saved image.
* Only saves images if the current asset list hash differs from the stored hash or the file doesn't exist.
* @param params An object containing the image URL, asset information, and current asset list hash.
* @returns The filename of the saved image or null if skipped.
*/
export async function saveAssetImageToTokensDir(
imageUrl: string,
asset: Pick<Asset, "symbol">
) {
export async function saveAssetImageToTokensDir({
imageUrl,
asset,
currentAssetListHash,
}: {
imageUrl: string;
asset: Pick<Asset, "symbol">;
currentAssetListHash: string;
}) {
// Ensure the tokens directory exists.
if (!fs.existsSync(path.resolve() + "/public" + tokensDir)) {
fs.mkdirSync(path.resolve() + "/public" + tokensDir, { recursive: true });
Expand All @@ -56,6 +93,16 @@ export async function saveAssetImageToTokensDir(

if (process.env.NODE_ENV === "test") {
console.info("Skipping image download for test environment");
return null;
}

const storedHash = readStoredAssetListHash();

/**
* Skip saving the image if the current asset list hash matches the stored hash.
*/
if (storedHash === currentAssetListHash && fs.existsSync(filePath)) {
return null;
}

// Fetch the image from the URL.
Expand All @@ -80,7 +127,7 @@ export async function saveAssetImageToTokensDir(
).pipe(fileStream)
);

// verify the image has been added
// Verify the image has been added
if (!fs.existsSync(filePath)) {
throw new Error(`Failed to save image to ${filePath}`);
}
Expand Down
21 changes: 21 additions & 0 deletions packages/web/hooks/__tests__/use-convert-variant.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Dec } from "@keplr-wallet/unit";

import { checkLargeAmountDiff } from "../use-convert-variant";

describe("isLargeAmountDiff", () => {
test("returns false when input amount is zero", () => {
expect(checkLargeAmountDiff(new Dec("0"), new Dec("100"))).toBe(false);
});

test("returns false when output is 95% or more of input", () => {
expect(checkLargeAmountDiff(new Dec("100"), new Dec("95"))).toBe(false);
expect(checkLargeAmountDiff(new Dec("100"), new Dec("96"))).toBe(false);
expect(checkLargeAmountDiff(new Dec("100"), new Dec("100"))).toBe(false);
});

test("returns true when output is less than 95% of input", () => {
expect(checkLargeAmountDiff(new Dec("100"), new Dec("94"))).toBe(true);
expect(checkLargeAmountDiff(new Dec("100"), new Dec("90"))).toBe(true);
expect(checkLargeAmountDiff(new Dec("100"), new Dec("50"))).toBe(true);
});
});
20 changes: 19 additions & 1 deletion packages/web/hooks/use-convert-variant.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Dec } from "@keplr-wallet/unit";
import type { AssetVariant } from "@osmosis-labs/server";
import { getSwapMessages, type QuoteOutGivenIn } from "@osmosis-labs/tx";
import { useCallback, useMemo } from "react";
Expand Down Expand Up @@ -149,13 +150,20 @@ export function useConvertVariant(

const { fiatValue: feeFiatValue } = useCoinFiatValue(quote?.feeAmount);

// Check for large difference in amount of in v out
const isLargeAmountDiff = useMemo(() => {
const inAmount = variant?.amount.toDec();
const outAmount = quote?.amount.toDec();
return checkLargeAmountDiff(inAmount, outAmount);
}, [variant, quote?.amount]);

return {
onConvert,
quote,
convertFee: feeFiatValue,
variant,
isLoading: isQuoteLoading || isPortfolioAssetsLoading,
isError: isQuoteError || isPortfolioAssetsError,
isError: isLargeAmountDiff || isQuoteError || isPortfolioAssetsError,
/** Is any conversion in progress. */
isConvertingVariant: Boolean(
account?.txTypeInProgress.startsWith(transactionIdentifier)
Expand Down Expand Up @@ -210,3 +218,13 @@ export async function getConvertVariantMessages(
userOsmoAddress: address,
});
}

/**
* Checks if there is a large difference between input and output amounts
* Returns true if output amount is less than 95% of input amount
*/
export function checkLargeAmountDiff(inAmount?: Dec, outAmount?: Dec): boolean {
if (!inAmount || !outAmount || inAmount.isZero()) return false;
// Out amount should be 95% or more of the input amount
return outAmount.quo(inAmount).lt(new Dec("0.95"));
}
27 changes: 27 additions & 0 deletions packages/web/hooks/use-deep-memo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import equal from "fast-deep-equal";
import { DependencyList, useRef } from "react";

/**
* A custom hook for `useMemo` that uses deep comparison on the dependencies.
*
* @param factory - A function that produces the memoized value.
* @param dependencies - The dependency array to be deeply compared.
* @returns The memoized value.
*/
export function useDeepMemo<T>(
factory: () => T,
dependencies: DependencyList
): T {
if (!Array.isArray(dependencies)) {
throw new Error("useDeepMemo expects a dependency array");
}
const dependenciesRef = useRef<DependencyList>();
const memoizedValueRef = useRef<T>();

if (!equal(dependenciesRef.current, dependencies)) {
dependenciesRef.current = dependencies;
memoizedValueRef.current = factory();
}

return memoizedValueRef.current!;
}
Loading
Loading