Skip to content

Commit 2567ddb

Browse files
committed
feat: add stack in a pool page
1 parent e625b84 commit 2567ddb

File tree

14 files changed

+783
-17
lines changed

14 files changed

+783
-17
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { useStackingClient } from '~/features/stacking/providers/stacking-client-provider';
3+
4+
import {
5+
createGetAllowanceContractCallersQueryOptions,
6+
createGetCoreInfoQueryOptions,
7+
createGetCycleDurationQueryOptions,
8+
createGetPoxInfoQueryOptions,
9+
createGetPoxOperationInfoQueryOptions,
10+
createGetSecondsUntilNextCycleQueryOptions,
11+
createGetStatusQueryOptions,
12+
} from '@leather.io/query';
13+
14+
export function useGetAllowanceContractCallersQuery(
15+
...params: Parameters<typeof createGetAllowanceContractCallersQueryOptions>
16+
) {
17+
return useQuery(createGetAllowanceContractCallersQueryOptions(...params));
18+
}
19+
20+
export function useGetCycleDurationQuery() {
21+
const { client } = useStackingClient();
22+
if (!client) throw new Error('Expected client to be defined.');
23+
return useQuery(createGetCycleDurationQueryOptions({ client }));
24+
}
25+
26+
export function useGetStatusQuery() {
27+
const { client } = useStackingClient();
28+
if (!client) throw new Error('Expected client to be defined.');
29+
return useQuery(createGetStatusQueryOptions({ client }));
30+
}
31+
32+
export function useGetPoxOperationInfoQuery() {
33+
const { client } = useStackingClient();
34+
if (!client) throw new Error('Expected client to be defined.');
35+
return useQuery(createGetPoxOperationInfoQueryOptions({ client }));
36+
}
37+
38+
export function useGetCoreInfo() {
39+
const { client } = useStackingClient();
40+
if (!client) throw new Error('Expected client to be defined.');
41+
return useQuery(createGetCoreInfoQueryOptions({ client }));
42+
}
43+
44+
export function useGetSecondsUntilNextCycleQuery() {
45+
const { client } = useStackingClient();
46+
if (!client) throw new Error('Expected client to be defined.');
47+
return useQuery(createGetSecondsUntilNextCycleQueryOptions({ client }));
48+
}
49+
50+
export function useGetPoxInfoQuery() {
51+
const { client } = useStackingClient();
52+
if (!client) throw new Error('Expected client to be defined.');
53+
return useQuery(createGetPoxInfoQueryOptions({ client }));
54+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ReactNode, createContext, useContext, useMemo } from 'react';
2+
3+
import { StackingClient } from '@stacks/stacking';
4+
import { validateStacksAddress as isValidStacksAddress } from '@stacks/transactions';
5+
import { useLeatherConnect } from '~/store/addresses';
6+
import { useStacksNetwork } from '~/store/stacks-network';
7+
8+
interface StackingClientContext {
9+
client: null | StackingClient;
10+
}
11+
const StackingClientContext = createContext<StackingClientContext>({
12+
client: null,
13+
});
14+
15+
interface Props {
16+
children: ReactNode;
17+
}
18+
19+
export function StackingClientProvider({ children }: Props) {
20+
const { stxAddress } = useLeatherConnect();
21+
const { network } = useStacksNetwork();
22+
23+
const client = useMemo<StackingClient | null>(() => {
24+
if (stxAddress && isValidStacksAddress(stxAddress.address)) {
25+
return new StackingClient({ address: stxAddress.address, network });
26+
}
27+
28+
return null;
29+
}, [stxAddress, network]);
30+
31+
return (
32+
<StackingClientContext.Provider value={{ client }}>{children}</StackingClientContext.Provider>
33+
);
34+
}
35+
36+
export function useStackingClient() {
37+
return useContext(StackingClientContext);
38+
}

apps/web/app/features/stacking/utils/types-preset-pools.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
export type NetworkInstance = 'mainnet' | 'testnet' | 'devnet';
22

3-
export type PoolName =
4-
| 'FAST Pool'
5-
| 'Xverse'
6-
| 'PlanBetter'
7-
| 'Restake'
8-
| 'Stacking DAO'
9-
| 'Custom Pool';
3+
export const PoolNameSlugMap = {
4+
fastPool: 'FAST Pool',
5+
xverse: 'Xverse',
6+
planBetter: 'PlanBetter',
7+
restake: 'Restake',
8+
stackingDao: 'Stacking DAO',
9+
custom: 'Custom Pool',
10+
} as const;
11+
12+
export type PoolSlug = keyof typeof PoolNameSlugMap;
13+
export type PoolName = (typeof PoolNameSlugMap)[PoolSlug];
1014

1115
export type PoxContractName =
1216
| 'WrapperOneCycle'

apps/web/app/helpers/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { isBrowser } from '@leather.io/sdk';
12
import { isDefined } from '@leather.io/utils';
23

34
export function isLeatherInstalled() {
4-
return isDefined(window.LeatherProvider);
5+
return isBrowser() && isDefined(window.LeatherProvider);
56
}
67

78
export type ExtensionState = 'missing' | 'detected' | 'connected';

apps/web/app/pages/stacking/components/earn-provider-table.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ReactElement, useMemo, useState } from 'react';
2+
import { Link } from 'react-router';
23

34
import {
45
type ColumnDef,
@@ -15,6 +16,7 @@ import { BitcoinIcon } from '~/components/icons/bitcoin-icon';
1516
import { StacksIcon } from '~/components/icons/stacks-icon';
1617
import { ImgFillLoader } from '~/components/img-loader';
1718
import { SortableHeader, Table, rowPadding, theadBorderBottom } from '~/components/table';
19+
import { PoolSlug } from '~/features/stacking/utils/types-preset-pools';
1820

1921
import { Button, Flag } from '@leather.io/ui';
2022

@@ -50,6 +52,7 @@ interface EarnProvider {
5052
estApr: string;
5153
payout: string;
5254
icon: ReactElement;
55+
slug: PoolSlug;
5356
}
5457
const earnProviders: EarnProvider[] = [
5558
{
@@ -58,34 +61,39 @@ const earnProviders: EarnProvider[] = [
5861
minAmount: null,
5962
estApr: '5%',
6063
payout: 'STX',
64+
slug: 'fastPool',
6165
},
6266
{
6367
provider: 'PlanBetter',
6468
icon: <ImgFillLoader src="icons/planbetter.webp" width="24" fill="black" />,
6569
minAmount: '200 STX',
6670
estApr: '10%',
6771
payout: 'STX',
72+
slug: 'planBetter',
6873
},
6974
{
7075
provider: 'Restake',
7176
icon: <ImgFillLoader src="icons/restake.webp" width="24" fill="#124044" />,
7277
minAmount: '100 STX',
7378
estApr: '11%',
7479
payout: 'STX',
80+
slug: 'restake',
7581
},
7682
{
7783
provider: 'Xverse Pool',
7884
icon: <ImgFillLoader src="icons/xverse.webp" width="24" fill="black" />,
7985
minAmount: '100 STX',
8086
estApr: '10%',
8187
payout: 'BTC',
88+
slug: 'xverse',
8289
},
8390
{
8491
provider: 'Stacking DAO',
8592
icon: <ImgFillLoader src="icons/stacking-dao.webp" width="24" fill="#1C3830" />,
8693
minAmount: '100 STX',
8794
estApr: '16%',
8895
payout: 'STX',
96+
slug: 'stackingDao',
8997
},
9098
];
9199

@@ -145,9 +153,14 @@ export function EarnProviderTable(props: HTMLStyledProps<'div'>) {
145153
{info.getValue() as string}
146154
</Flag>
147155

148-
<Button size="xs" ml="space.04" minW="fit-content">
149-
Start earning
150-
</Button>
156+
<Link
157+
to={`/stacking/stack-in-pool/${info.row.original.slug}`}
158+
style={{ minWidth: 'fit-content' }}
159+
>
160+
<Button size="xs" ml="space.04" minW="fit-content">
161+
Start earning
162+
</Button>
163+
</Link>
151164
</Flex>
152165
),
153166
meta: { align: 'left' },

apps/web/app/root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Buffer } from 'safe-buffer';
2+
// @ts-expect-error // import process using alias
3+
import process from 'safe-process';
24

35
import leatherUiStyles from '@leather.io/ui/styles?url';
46

@@ -12,6 +14,12 @@ import stylesheet from './app.css?url';
1214
// @ts-expect-error // safe-buffer typings are too old
1315
globalThis.Buffer = Buffer;
1416

17+
/**
18+
* Polyfill global process
19+
* (should be called before another code)
20+
*/
21+
globalThis.process = process;
22+
1523
export function links() {
1624
return [
1725
{ rel: 'stylesheet', href: stylesheet },

apps/web/app/routes.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { type RouteConfig, index, route } from '@react-router/dev/routes';
1+
import { type RouteConfig, index, prefix, route } from '@react-router/dev/routes';
22

33
export default [
44
index('routes/home.tsx'),
5-
route('stacking', 'routes/stacking.tsx'),
6-
route('sbtc-rewards', 'routes/sbtc-rewards.tsx'),
5+
...prefix('stacking', [
6+
index('routes/stacking/index.tsx'),
7+
route('stack-in-pool/:slug', 'routes/stacking/stacking.tsx'),
8+
]),
79
route('docs', 'routes/docs.tsx'),
810
] satisfies RouteConfig;

apps/web/app/routes/stacking.tsx renamed to apps/web/app/routes/stacking/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Stacking } from '~/pages/stacking/stacking';
22

33
import { delay } from '@leather.io/utils';
44

5-
import type { Route } from './+types/stacking';
5+
import type { Route } from '../+types/home';
66

77
export async function loader() {
88
await delay(400);
@@ -17,6 +17,6 @@ export function meta({}: Route.MetaArgs) {
1717
];
1818
}
1919

20-
export default function StackingRoute() {
20+
export default function HomeRoute() {
2121
return <Stacking />;
2222
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { StackingClientProvider } from '~/features/stacking/providers/stacking-client-provider';
2+
import { PoolNameSlugMap, PoolSlug } from '~/features/stacking/utils/types-preset-pools';
3+
4+
import { delay } from '@leather.io/utils';
5+
6+
import type { Route } from './+types/stacking';
7+
8+
export async function loader() {
9+
await delay(400);
10+
return true;
11+
}
12+
13+
// eslint-disable-next-line no-empty-pattern
14+
export function meta({}: Route.MetaArgs) {
15+
return [{ title: 'Leather Earn - Stacking' }];
16+
}
17+
18+
export default function EarnStackingRoute({ params }: Route.ComponentProps) {
19+
const poolSlug = params.slug as PoolSlug;
20+
const poolName = PoolNameSlugMap[poolSlug];
21+
22+
if (!poolName) throw new Error(`Uknown pool - ${poolSlug}`);
23+
24+
return (
25+
<StackingClientProvider>
26+
<div>Stacking</div>
27+
</StackingClientProvider>
28+
);
29+
}

apps/web/app/store/addresses.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useMemo } from 'react';
2+
13
import { atom, useAtom, useAtomValue } from 'jotai';
24
import { atomWithStorage } from 'jotai/utils';
35
import { leather } from '~/helpers/leather-sdk';
@@ -17,10 +19,29 @@ export const extensionStateAtom = atom<ExtensionState>(get => {
1719
export function useLeatherConnect() {
1820
const [addresses, setAddresses] = useAtom(addressesAtom);
1921
const status = useAtomValue(extensionStateAtom);
22+
23+
const stxAddress = useMemo(
24+
() => addresses.find(address => address.symbol === 'STX'),
25+
[addresses]
26+
);
27+
28+
const btcAddressP2tr = useMemo(
29+
() => addresses.find(address => address.type === 'p2tr'),
30+
[addresses]
31+
);
32+
33+
const btcAddressP2wpkh = useMemo(
34+
() => addresses.find(address => address.type === 'p2wpkh'),
35+
[addresses]
36+
);
37+
2038
return {
2139
addresses,
2240
setAddresses,
2341
status,
42+
stxAddress,
43+
btcAddressP2tr,
44+
btcAddressP2wpkh,
2445
async connect() {
2546
const result = await leather.getAddresses();
2647
setAddresses(result.addresses);

0 commit comments

Comments
 (0)