Skip to content

Commit

Permalink
feat: quests page (#30)
Browse files Browse the repository at this point in the history
* feat: prepare for the quests page

* feat: quests setup and port wallet install page

* feat: port the rest of the pages and install and use biome

* feat: add tailwindˆ

* feat: remove chakra & add swap component

* feat: remove chakra & merge pages

* feat: fix styles everywhere

* fix: build

* fixes

* fix: open links in new tab

* feat: show balances

* fix: deposit styling in dark mode

* fix: deposits monitoring

* fix: deposits display

---------

Co-authored-by: Atris <[email protected]>
  • Loading branch information
VanishMax and vacekj authored Oct 18, 2024
1 parent 45cedd9 commit 33baea4
Show file tree
Hide file tree
Showing 31 changed files with 6,895 additions and 1,323 deletions.
29 changes: 29 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space",
"enabled": true
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
},
"linter": {
"enabled": false,
"rules": {
"recommended": true,
"style": {
"noNonNullAssertion": "off"
}
}
}
}
34 changes: 34 additions & 0 deletions components/Balances.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useBalances } from '@/components/hooks';
import type { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { ValueViewComponent } from '@penumbra-zone/ui/ValueViewComponent';
import type React from 'react';

export function Balances() {
const { data: balances } = useBalances();

return balances
? balances
.map((bal) => bal.balanceView)
.map((balanceView) => (
<BalanceRow
key={balanceView!.toJsonString()}
balance={balanceView!}
/>
))
: null;
}

function BalanceRow({
balance,
}: {
balance: ValueView;
}) {
return (
<div
className="mt-3 flex gap-3 items-center bg-gray-700 text-white p-3"
key={balance.toJsonString()}
>
<ValueViewComponent valueView={balance} />
</div>
);
}
253 changes: 253 additions & 0 deletions components/Deposit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import { client } from '@/components/penumbra';
import { useQuestStore } from '@/components/store';
import { ViewService } from '@penumbra-zone/protobuf';
import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import type { CommitmentSource_Ics20Transfer } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb';
import { AddressView } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb';
import type { NotesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';
import { AddressViewComponent } from '@penumbra-zone/ui/AddressViewComponent';
import { ValueViewComponent } from '@penumbra-zone/ui/ValueViewComponent';
import { useQuery } from '@tanstack/react-query';
import { capitalize } from 'es-toolkit';
import { ChevronRightIcon } from 'lucide-react';
import React, { useState } from 'react';
import {
useConnect,
useCurrentChainStatus,
useEphemeralAddress,
useNotes,
useSetScanSinceBlock,
useSwaps,
useWalletManifests,
} from './hooks';

const Deposit: React.FC = () => {
useSetScanSinceBlock();
const [showOld, setShowOld] = useState(false);

const { data } = useNotes();
const { connected, onConnect, connectionLoading } = useConnect();
const { data: wallets, isLoading } = useWalletManifests();

const { data: status } = useCurrentChainStatus();
const currentBlock = BigInt(status?.syncInfo?.latestBlockHeight ?? 0);
const depositNotes =
data?.filter(
(note) => note?.noteRecord?.source?.source.case === 'ics20Transfer',
) ?? [];

const { scanSinceBlockHeight } = useQuestStore();
console.log(showOld);
const { data: notesWithMetadata } = useQuery({
queryKey: [
'notesWithMetadata',
currentBlock.toString(),
showOld,
depositNotes,
connected,
],
staleTime: 0,
initialData: [],
queryFn: async () => {
console.log('refetch');
const deposits = await Promise.all(
depositNotes.map(async (note) => {
const metadata = await client.service(ViewService).assetMetadataById({
assetId: note.noteRecord?.note?.value?.assetId!,
});

return {
metadata,
note,
valueView: new ValueView({
valueView: {
case: 'knownAssetId',
value: {
metadata: metadata.denomMetadata!,
amount: note?.noteRecord?.note?.value?.amount!,
},
},
}),
};
}),
);
return deposits.filter(
(d) =>
Number(d.note!.noteRecord!.heightCreated) >
(showOld ? 0 : scanSinceBlockHeight),
);
},
});

const { data: ibcInAddress } = useEphemeralAddress({
index: 0,
});

return (
<div className="py-3 flex flex-col gap-8">
<div>
Now it's time to shield your funds and transfer them into Penumbra.
We've displayed one of your IBC Deposit addresses for you convenience
below. Copy it using the button on the right.
</div>

{!isLoading &&
wallets &&
!connected &&
Object.entries(wallets).map(([origin, manifest]) => (
<button
// type={'button'}
key={origin}
onClick={() => onConnect(origin)}
disabled={connectionLoading}
className="bg-blue-700 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
>
{connectionLoading
? 'Connecting...'
: `Connect to ${manifest.name}`}
</button>
))}

{ibcInAddress?.address && connected && (
<div className={'bg-gray-700 p-3'}>
<AddressViewComponent
addressView={
new AddressView({
addressView: {
case: 'decoded',
value: {
address: ibcInAddress.address,
},
},
})
}
/>
</div>
)}

<div
className="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4"
role="alert"
>
<p className="font-bold">Info</p>
<p>
IBC Deposit addresses exist because source chains record the deposit
address. They serve as an additional layer of anonymity to not link
your deposit and actual addresses.
</p>
</div>

<div>
We will use&nbsp;
<a
href="https://go.skip.build/"
className="font-medium underline"
target="_blank"
rel="noopener noreferrer"
>
Skip Protocol
</a>
&nbsp; to bridge funds into Penumbra. Go to the Skip app, and input your
IBC Deposit address. Select your source chain and asset (we recommend
USDC, but any common asset is fine) and select Penumbra and USDC as the
destination chain. Then initiate the deposit and come back to this page.
</div>

<div
className="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4"
role="alert"
>
<p className="font-bold">Info</p>
<p>
Penumbra supports paying fees in multiple tokens, including USDC. Prax
will always choose the best token to pay fees with depending on your
balances.
</p>
</div>

{notesWithMetadata.length === 0 && (
<div className="w-full bg-white text-black shadow-md rounded-lg p-4">
<div className="flex flex-row gap-3 items-center">
<div>Waiting for a deposit to occur</div>
<div className="animate-spin h-5 w-5 border-2 border-blue-500 rounded-full border-t-transparent" />
</div>
</div>
)}

{notesWithMetadata.length > 0 && (
<div className="border-0">
<div className="mt-3 group-open:animate-fadeIn">
{notesWithMetadata.length > 0 &&
notesWithMetadata?.map(({ note, valueView }) => (
<DepositRow
key={note.toJsonString()}
note={note}
valueView={valueView}
/>
))}
</div>
</div>
)}

<div className={'flex items-center'}>
<input
id="default-checkbox"
checked={showOld}
type={'checkbox'}
onChange={(e) => setShowOld((old) => !old)}
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
/>
<label
htmlFor="default-checkbox"
className="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Show old deposits
</label>
</div>
</div>
);
};

function DepositRow({
note,
valueView,
}: {
note: NotesResponse;
valueView: ValueView;
}) {
const source = note.noteRecord?.source?.source
?.value as CommitmentSource_Ics20Transfer;
const chainId = source.sender.replace(/^(\D+)(\d).*$/, '$1-$2');

const chainName = capitalize(source.sender.replace(/^(\D+).*$/, '$1'));
return (
<div
className="mt-3 flex gap-3 items-center bg-gray-700 text-white p-3"
key={note.toJsonString()}
>
Deposited
<ValueViewComponent key={note.toJsonString()} valueView={valueView} />
from {chainName}
<ChevronRightIcon className="h-4 w-4" />
<a
className="underline"
target="_blank"
rel="noopener noreferrer"
href={`https://ibc.range.org/ibc/status?id=${chainIdToExplorerChainName(chainId)}/${source.channelId}/${source.packetSeq}`}
>
Inspect deposit
</a>
</div>
);
}

function chainIdToExplorerChainName(chainId: string) {
switch (chainId) {
case 'osmo-1':
return 'osmosis-1';
default:
return chainId;
}
}

export default Deposit;
57 changes: 57 additions & 0 deletions components/Disconnect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type React from 'react';
import { useEffect, useRef } from 'react';
import { useBalances, useConnect } from './hooks';

const Disconnect: React.FC = () => {
const { connected } = useConnect();

return (
<div className="py-3 flex flex-col gap-8">
<div>
Once you are done working with a page, you can disconnect your wallet.
To do this in Prax. You can go to the Settings, click Connected sites,
and click the trash button next to the site URL. This will disconnect
the extension from the site, after which the site can no longer access
your data.
</div>
{connected && (
<div className="w-full bg-white shadow-md rounded-lg p-4">
<div className="flex flex-row gap-3 items-center">
<div>
Waiting for extension to disconnect. Refresh the page once
disconnected.
</div>
<div className="animate-spin h-5 w-5 border-2 border-blue-500 rounded-full border-t-transparent" />
</div>
</div>
)}
{!connected && (
<div>Congratulations. The site can no longer access your data.</div>
)}
</div>
);
};

type IntervalFunction = () => void;

function useInterval(callback: IntervalFunction, delay: number | null) {
const savedCallback = useRef<IntervalFunction>();

// Remember the latest callback
useEffect(() => {
savedCallback.current = callback;
}, [callback]);

// Set up the interval
useEffect(() => {
function tick() {
savedCallback.current?.();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}

export default Disconnect;
Loading

0 comments on commit 33baea4

Please sign in to comment.