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

Dynamic client side envs 2 #80

Merged
merged 15 commits into from
Oct 8, 2024
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
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[];
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: it is tempting to use our own types, but we should do our best to use types from BufBuild that are more universally relied upon in the penumbra ecosystem. For instance, we shouldn't have components that accept a Token, but probably ones that accept a ValueView type


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
Loading