Skip to content

Commit

Permalink
Dynamic client side envs 2 (#80)
Browse files Browse the repository at this point in the history
* Implement dynamic client side envs

* Remove configConstants

* Fix lint issue

* docs: update readme with new env vars

* Add useEnv fetcher

* Refactor app to use fetchers

* Remove ClientEnv type file

* Fix merge conflict + view pool bug

* Add defaults for client env

* Fix type import

* Refactor fetchers to suffix Deprecated

* Fix duplicate lpAssetView.tsx file

* Fix lint issues

* Fix lp asset view

---------

Co-authored-by: Conor Schaefer <[email protected]>
  • Loading branch information
JasonMHasperhoven and conorsch authored Oct 8, 2024
1 parent bdf9ab6 commit df94a11
Show file tree
Hide file tree
Showing 27 changed files with 298 additions and 234 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PENUMBRA_GRPC_ENDPOINT=
PENUMBRA_INDEXER_ENDPOINT=
PENUMBRA_INDEXER_CA_CERT=
PENUMBRA_CHAIN_ID=
PENUMBRA_CUILOA_URL=
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ or plug in credentials for an already running database via environment variables
# add these to e.g. `.envrc`:
export PENUMBRA_GRPC_ENDPOINT="https://testnet.plinfra.net"
export PENUMBRA_INDEXER_ENDPOINT="postgresql://<PGUSER>:<PGPASS>@<PGHOST>:<PGPORT>/<PGDATABASE>?sslmode=require""
export NEXT_PUBLIC_CHAIN_ID="penumbra-testnet-phobos-2"
export PENUMBRA_CHAIN_ID="penumbra-testnet-phobos-2"
# optional: if you see "self-signed certificate in certificate chain" errors,
# you'll likely need to export a `ca-cert.pem` file for the DB TLS.
# export PENUMBRA_INDEXER_CA_CERT="$(cat ca-cert.pem)"
Expand All @@ -49,8 +49,8 @@ you'll want to set are:
* `PENUMBRA_GRPC_ENDPOINT`: the URL to a remote node's `pd` gRPC service
* `PENUMBRA_INDEXER_ENDPOINT`: the URL to a Postgre database containing ABCI events
* `PENUMBRA_INDEXER_CA_CERT`: optional; if set, the database connection will use the provided certificate authority when validating TLS
* `NEXT_PUBLIC_CHAIN_ID`: the chain id for the network being indexed, controls asset-registry lookups
* `NEXT_PUBLIC_CUILOA_URL`: the URL for a block-explorer application, for generating URLs for more block/transaction info
* `PENUMBRA_CHAIN_ID`: the chain id for the network being indexed, controls asset-registry lookups
* `PENUMBRA_CUILOA_URL`: the URL for a block-explorer application, for generating URLs for more block/transaction info

## Name

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@radix-ui/react-icons": "^1.3.0",
"@rehooks/component-size": "^1.0.3",
"@styled-icons/octicons": "^10.47.0",
"@tanstack/react-query": "^5.59.0",
"@tsconfig/strictest": "^2.0.5",
"@tsconfig/vite-react": "^3.0.2",
"@vercel/analytics": "^1.3.1",
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/fetchers/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useQuery } from '@tanstack/react-query';
import { ClientEnv } from '@/utils/env/types';

export const useEnv = () => {
return useQuery({
queryKey: ['clientEnv'],
queryFn: async (): Promise<ClientEnv> => {
const res = await fetch('/api/env');
return (await res.json()) as ClientEnv;
},
staleTime: Infinity,
});
};
32 changes: 32 additions & 0 deletions src/fetchers/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ChainRegistryClient, Registry } from '@penumbra-labs/registry';
import { useQuery } from '@tanstack/react-query';
import { useEnv } from './env';

export const chainRegistryClient = new ChainRegistryClient();

export const useRegistry = () => {
const { data: env, isLoading: isEnvLoading, error: envError } = useEnv();

const {
data: registry,
isLoading: isRegistryLoading,
error: registryError,
} = useQuery({
queryKey: ['penumbraRegistry', env],
queryFn: async (): Promise<Registry> => {
const chainId = env?.PENUMBRA_CHAIN_ID;
if (!chainId) {
throw new Error('chain id not available to query registry');
}
return chainRegistryClient.remote.get(chainId);
},
staleTime: Infinity,
enabled: Boolean(env),
});

return {
data: registry,
isLoading: isEnvLoading || isRegistryLoading,
error: envError ?? registryError,
};
};
80 changes: 80 additions & 0 deletions src/fetchers/tokenAssets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useRegistry } from './registry';
import { AssetId, Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { decimalsFromDenomUnits, imagePathFromAssetImages } from '@/old/utils/token/tokenFetch';
import { uint8ArrayToBase64, base64ToUint8Array } from '@/old/utils/math/base64';
import { Token } from '@/old/utils/types/token';

export const useTokenAssets = () => {
const { data: registry, isLoading: isRegistryLoading, error: registryError } = useRegistry();
const data: Metadata[] = registry?.getAllAssets() ?? [];

return {
data,
isLoading: isRegistryLoading,
error: registryError,
};
};

export const useTokenAsset = (tokenId: Uint8Array | string) => {
const { data: registry, isLoading: isRegistryLoading, error: registryError } = useRegistry();

const assetId: AssetId = new AssetId();
assetId.inner = typeof tokenId !== 'string' ? tokenId : base64ToUint8Array(tokenId);
const tokenMetadata = registry?.getMetadata(assetId);

return {
data: tokenMetadata,
isLoading: isRegistryLoading,
error: registryError,
};
};

export const useTokenAssetsDeprecated = () => {
const { data: registry, isLoading: isRegistryLoading, error: registryError } = useRegistry();
const assets: Metadata[] = registry?.getAllAssets() ?? [];

const tokenAssets = assets
.filter(asset => asset.penumbraAssetId && !asset.display.startsWith('delegation_'))
.map(asset => {
const displayParts = asset.display.split('/');
return {
decimals: decimalsFromDenomUnits(asset.denomUnits),
display: displayParts[displayParts.length - 1] ?? '',
symbol: asset.symbol,
inner: asset.penumbraAssetId?.inner && uint8ArrayToBase64(asset.penumbraAssetId.inner),
imagePath: imagePathFromAssetImages(asset.images),
};
}) as Token[];

return {
data: tokenAssets,
isLoading: isRegistryLoading,
error: registryError,
};
};

export const useTokenAssetDeprecated = (tokenId: Uint8Array | string) => {
const { data: registry, isLoading: isRegistryLoading, error: registryError } = useRegistry();

let tokenAsset = undefined;
if (registry) {
const assetId: AssetId = new AssetId();
assetId.inner = typeof tokenId !== 'string' ? tokenId : base64ToUint8Array(tokenId);
const tokenMetadata = registry.getMetadata(assetId);

const displayParts = tokenMetadata.display.split('/');
tokenAsset = {
decimals: decimalsFromDenomUnits(tokenMetadata.denomUnits),
display: displayParts[displayParts.length - 1] ?? '',
symbol: tokenMetadata.symbol,
inner: typeof tokenId !== 'string' ? uint8ArrayToBase64(tokenId) : tokenId,
imagePath: imagePathFromAssetImages(tokenMetadata.images),
};
}

return {
data: tokenAsset,
isLoading: isRegistryLoading,
error: registryError,
};
};
5 changes: 3 additions & 2 deletions src/old/components/copiedTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { FC, useState } from "react";
import { CopyIcon } from "@radix-ui/react-icons";
import { HStack } from "@chakra-ui/react";
import { Constants } from "@/old/utils/configConstants";
import { useEnv } from "@/fetchers/env";

interface CopyTxToClipboardProps {
txHash: string;
Expand All @@ -14,6 +14,7 @@ const CopyTxToClipboard: FC<CopyTxToClipboardProps> = ({
txHash,
clipboardPopupText,
}) => {
const env = useEnv();
const [isCopied, setIsCopied] = useState(false);

const handleCopy = () => {
Expand All @@ -26,7 +27,7 @@ const CopyTxToClipboard: FC<CopyTxToClipboardProps> = ({
return (
<HStack align={"center"} spacing={".5em"}>
<a
href={`${Constants.cuiloaUrl}/transaction/${txHash}`}
href={`${env?.PENUMBRA_CUILOA_URL}/transaction/${txHash}`}
target="_blank"
rel="noreferrer"
style={{
Expand Down
89 changes: 22 additions & 67 deletions src/old/components/liquidityPositions/currentStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,14 @@ import { fetchTokenAsset } from "@/old/utils/token/tokenFetch";
import BigNumber from "bignumber.js";
import { CopyIcon } from "@radix-ui/react-icons";
import { Token } from "@/old/utils/types/token";
import { useTokenAssetDeprecated } from "@/fetchers/tokenAssets";

interface CurrentLPStatusProps {
nftId: string;
position: Position;
}

const CurrentLPStatus = ({ nftId, position }: CurrentLPStatusProps) => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isCopied, setIsCopied] = useState<boolean>(false);

const handleCopy = () => {
navigator.clipboard.writeText(nftId).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1500); // Hide popup after 1.5 seconds
});
};

// First process position to human readable pieces

function getStatusText(position): string {
// Get status
const status = (position.state!).state.toString();

Expand All @@ -57,65 +46,30 @@ const CurrentLPStatus = ({ nftId, position }: CurrentLPStatusProps) => {
statusText = "Unknown";
}

return statusText;
}

const CurrentLPStatus = ({ nftId, position }: CurrentLPStatusProps) => {
const [isCopied, setIsCopied] = useState<boolean>(false);

const handleCopy = () => {
navigator.clipboard.writeText(nftId).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1500); // Hide popup after 1.5 seconds
});
};

// First process position to human readable pieces
const statusText = getStatusText(position);

// Get fee tier
const feeTier = Number(position.phi!.component!.fee);

const asset1 = position.phi!.pair!.asset1;
const asset2 = position.phi!.pair!.asset2;

// States for tokens
const [asset1Token, setAsset1Token] = useState<Token>({
symbol: "UNKNOWN",
display: "UNKNOWN",
decimals: 0,
inner: "UNKNOWN",
imagePath: "UNKNOWN",
});
const [asset2Token, setAsset2Token] = useState<Token>({
symbol: "UNKNOWN",
display: "UNKNOWN",
decimals: 0,
inner: "UNKNOWN",
imagePath: "UNKNOWN",
});
const { asset1, asset2 } = position.phi!.pair!;
const { data: asset1Token } = useTokenAssetDeprecated(asset1.inner);
const { data: asset2Token } = useTokenAssetDeprecated(asset2.inner);
const [assetError, setAssetError] = useState<string | undefined>();

useEffect(() => {
// Function to fetch tokens asynchronously
const fetchTokens = async () => {
try {
const asset1 = position.phi!.pair!.asset1;
const asset2 = position.phi!.pair!.asset2;

if (asset1?.inner) {
const fetchedAsset1Token = fetchTokenAsset(asset1.inner);
if (!fetchedAsset1Token) {
setAssetError("Asset 1 token not found");
throw new Error("Asset 1 token not found");
}
setAsset1Token(fetchedAsset1Token);
}

if (asset2?.inner) {
const fetchedAsset2Token = fetchTokenAsset(asset2.inner);
if (!fetchedAsset2Token) {
setAssetError("Asset 2 token not found");
throw new Error("Asset 2 token not found");
}
setAsset2Token(fetchedAsset2Token);
}
} catch (error) {
console.error(error);
}
};

fetchTokens();
}, [position]);

if (!isLoading && (!asset1Token || !asset2Token)) {
return <div>{`LP exists, but ${assetError}.`}</div>;
}

const reserves1 = fromBaseUnit(
BigInt(position.reserves!.r1?.lo || 0),
BigInt(position.reserves!.r1?.hi || 0),
Expand All @@ -133,6 +87,7 @@ const CurrentLPStatus = ({ nftId, position }: CurrentLPStatusProps) => {
BigInt(position.phi!.component!.p!.hi || 0),
asset2Token.decimals
);

const q: BigNumber = fromBaseUnit(
BigInt(position.phi!.component!.q!.lo || 0),
BigInt(position.phi!.component!.q!.hi || 0),
Expand Down
Loading

0 comments on commit df94a11

Please sign in to comment.