Skip to content

Commit b19b5ca

Browse files
committedSep 13, 2024
starter template
1 parent 8449dde commit b19b5ca

24 files changed

+9739
-0
lines changed
 

‎.DS_Store

6 KB
Binary file not shown.

‎frontend/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.env
3+
.next

‎frontend/.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.next
2+
node_modules

‎frontend/.prettierrc

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"jsxSingleQuote": false,
3+
"singleQuote": true,
4+
"tabWidth": 2,
5+
"trailingComma": "all",
6+
"useTabs": false
7+
}

‎frontend/README.md

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Portal Hackathon Kit - Web
2+
3+
![PayPal Logo](https://cdn.prod.website-files.com/66a9400bd5456b4248f11c92/66a940ca7f391719bd5ba2db_PayPal%201.png)
4+
![Portal Logo](https://cdn.prod.website-files.com/66a9400bd5456b4248f11c92/66a940c97f391719bd5ba2b9_Portal%20logo%201.png)
5+
![Solana Logo](<https://cdn.prod.website-files.com/66a9400bd5456b4248f11c92/66a940ca7f391719bd5ba2c5_Solana%20(SOL)%201.png>)
6+
7+
This is simple repo used to build an application on top of PayPal's PYUSD stablecoin, on the Solana blockchain, using Portal's MPC wallet infrastructure.
8+
9+
## Requirements
10+
11+
Before going through the quick start you'll need following tools with the minimum versions:
12+
13+
- git
14+
- Node (^20.16.0)
15+
- yarn or npm
16+
- [Portal Account](https://docs.portalhq.io/support/pyusd-hackathon-hub)
17+
- [Portal Client API Key](https://docs.portalhq.io/resources/authentication-and-api-keys#creating-a-test-client-api-key)
18+
19+
## Getting Started
20+
21+
To get spun up follow these steps.
22+
23+
1. Clone the repo and install dependencies.
24+
```bash
25+
git clone https://github.com/portal-hq/portal-hackathon-kit-web
26+
cd portal-hackathon-kit-web
27+
yarn
28+
```
29+
2. Paste your Portal Client API Key into the `next.config.mjs` file.
30+
3. Run the example app.
31+
```bash
32+
yarn dev
33+
```
34+
4. Press "Get Solana Wallet" to create a wallet. You'll see your Solana address.
35+
5. Take your address and go to the [PYUSD faucet](https://faucet.paxos.com/) to get some test PYUSD.
36+
37+
## Understanding the Repo
38+
39+
- `pages` and `components` contain files for all the [web pages or routes](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts) and reusable [React components](https://react.dev/learn/your-first-component)
40+
- `providers` contains [React contexts and respective providers](https://react.dev/learn/passing-data-deeply-with-context) i.e. shared state and logic for use across the app
41+
- `public` contains all the [static images (or assets)](https://nextjs.org/docs/app/building-your-application/optimizing/static-assets) used
42+
- `theme` contains [MUI theming context](https://mui.com/material-ui/customization/theming/) which defines the base style for UI elements
43+
- `next.config.mjs` contains [non-sensitive environment variables](https://nextjs.org/docs/pages/api-reference/next-config-js/env)
44+
45+
## Portal & PYUSD Documentation
46+
47+
### Portal SDK Reference
48+
49+
Portal's SDKs have several pieces of core functionality.
50+
51+
- [Generating a Wallet](https://docs.portalhq.io/guides/web/create-a-wallet): This function creates MPC key shares on your local device and the Portal servers. These key shares support all EVM chains and Solana.
52+
- [Signing a Transaction](https://docs.portalhq.io/guides/web/sign-a-transaction): This function signs a provided transaction, and can broadcast that transaction to a chain when an RPC gateway URL is provided.
53+
- [Signature Hooks](https://docs.portalhq.io/guides/web/add-custom-signature-hooks): By default this repo will submit a transaction without prompting a user, but you can use signature hooks to build a prompt for users before submitting a transaction for signing.
54+
55+
### Portal APIs
56+
57+
Portal supplies several APIs for simplifying your development.
58+
59+
- [Get Assets](https://docs.portalhq.io/reference/client-api/v3-endpoints#get-assets-by-chain): This endpoint returns a list of fungible asset (native, ERC-20, and SPL tokens) associated with your client for a given chain.
60+
- [Get NFTs](https://docs.portalhq.io/reference/client-api/v3-endpoints#get-nft-assets-by-chain): This endpoint returns a list of the NFTs associated with your client for a given chain.
61+
- [Get Transactions](https://docs.portalhq.io/reference/client-api/v3-endpoints#get-transactions-by-chain): This endpoint returns a list of the historic transactions associated with your client for a given chain.
62+
- [Build a Transaction - Send Asset](https://docs.portalhq.io/reference/client-api/v3-endpoints#build-a-send-asset-transaction): This endpoint builds a formatted transaction to send a fungible asset (native, ERC-20, and SPL tokens) for a given chain.
63+
- [Evaluate a Transaction](https://docs.portalhq.io/reference/client-api/v3-endpoints#evaluate-a-transaction): This endpoint can simulate a transaction and/or scan a transaction for security concerns.
64+
65+
### PYUSD Documentation
66+
67+
- [PYUSD on Solana](https://solana.com/news/pyusd-paypal-solana-developer): An overview of PYUSD on Solana.
68+
- [PYUSD Quick Start Guide](https://developer.paypal.com/community/blog/pyusd-quick-start-guide/): A quick overview of PYUSD and information behind it.
69+
- [PYUSD Solana Testnet Faucet](https://faucet.paxos.com/): Use this faucet to get testnet PYUSD on Solana.
70+
- [What is PayPal USD](https://www.paypal.com/us/cshelp/article/what-is-paypal-usd-pyusd-help1005): Information about how PYUSD works.
71+
- [PYUSD Solana (SPL) Mainnet Address](https://explorer.solana.com/address/2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo): `2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo`
72+
- [PYUSD Solana (SPL) Devnet Address](https://explorer.solana.com/address/CXk2AMBfi3TwaEL2468s6zP8xq9NxTXjp9gjMgzeUynM?cluster=devnet): `CXk2AMBfi3TwaEL2468s6zP8xq9NxTXjp9gjMgzeUynM`
73+
74+
### Solana Documentation
75+
76+
- [Intro to Solana Development](https://solana.com/developers/guides/getstarted/hello-world-in-your-browser): An introduction to development on Solana.
77+
- [Solana SPL Token Docs](https://spl.solana.com/token): Documentation on SPL tokens.
78+
79+
### Faucets
80+
81+
- [SOL Faucet](https://faucet.solana.com/)
82+
- [PYUSD Faucet](https://faucet.paxos.com/)
83+
84+
### Other Helpful Resources
85+
86+
- [What is Portal MPC?](https://docs.portalhq.io/resources/portals-mpc-architecture)
87+
88+
## Help
89+
90+
Need help or want to request a feature? Reach out to the PayPal & Portal teams on the [official hackathon Slack channel](https://portalcommunity.slack.com/archives/C07EZFF9N78).

‎frontend/components/Layout.tsx

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import React from 'react';
2+
import { useEffect, useState, ReactNode } from 'react';
3+
import { styled, useTheme } from '@mui/material/styles';
4+
import Box from '@mui/material/Box';
5+
import Drawer from '@mui/material/Drawer';
6+
import CssBaseline from '@mui/material/CssBaseline';
7+
import MuiAppBar from '@mui/material/AppBar';
8+
import Toolbar from '@mui/material/Toolbar';
9+
import List from '@mui/material/List';
10+
import Divider from '@mui/material/Divider';
11+
import IconButton from '@mui/material/IconButton';
12+
import MenuIcon from '@mui/icons-material/Menu';
13+
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
14+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
15+
import ListItem from '@mui/material/ListItem';
16+
import ListItemButton from '@mui/material/ListItemButton';
17+
import ListItemText from '@mui/material/ListItemText';
18+
import { useRouter } from 'next/router';
19+
import { Button, Container, InputAdornment, TextField } from '@mui/material';
20+
import { usePortal } from '@/providers/portal';
21+
import { ContentCopy, Pending, Send } from '@mui/icons-material';
22+
import { useSnackbar } from '@/providers/snackbar';
23+
24+
const DRAWER_WIDTH = 240;
25+
const DRAWER_ITEMS = [
26+
{
27+
name: 'Dashboard',
28+
link: '/',
29+
},
30+
{
31+
name: 'Send Tokens',
32+
link: '/send',
33+
},
34+
];
35+
36+
const DrawerHeader = styled('div')(({ theme }) => ({
37+
display: 'flex',
38+
alignItems: 'center',
39+
padding: theme.spacing(0, 1),
40+
...theme.mixins.toolbar,
41+
justifyContent: 'flex-end',
42+
}));
43+
44+
export default function Layout({ children }: { children: ReactNode }) {
45+
const theme = useTheme();
46+
const router = useRouter();
47+
const snackbar = useSnackbar();
48+
49+
const portal = usePortal();
50+
const [solanaAddress, setSolanaAddress] = useState('');
51+
const [generatingSolanaAddress, setGeneratingSolanaAddress] = useState(false);
52+
53+
const [open, setOpen] = useState(false);
54+
55+
const handleDrawerOpen = () => {
56+
setOpen(true);
57+
};
58+
59+
const handleDrawerClose = () => {
60+
setOpen(false);
61+
};
62+
63+
const generateSolanaAddress = async () => {
64+
try {
65+
setGeneratingSolanaAddress(true);
66+
const address = await portal.getSolanaAddress();
67+
setSolanaAddress(address);
68+
69+
snackbar.setSnackbarOpen(true);
70+
snackbar.setSnackbarContent({
71+
severity: 'success',
72+
message: `Successfully generated Solana address`,
73+
});
74+
} catch (e) {
75+
snackbar.setSnackbarOpen(true);
76+
snackbar.setSnackbarContent({
77+
severity: 'error',
78+
message: `Something went wrong - ${e}`,
79+
});
80+
} finally {
81+
setGeneratingSolanaAddress(false);
82+
}
83+
};
84+
85+
useEffect(() => {
86+
if (portal.ready && !generatingSolanaAddress) {
87+
(async () => {
88+
await generateSolanaAddress();
89+
})();
90+
}
91+
}, [portal.ready, generatingSolanaAddress]);
92+
93+
return (
94+
<Box sx={{ display: 'flex' }}>
95+
<Container maxWidth="xl">
96+
<CssBaseline />
97+
<MuiAppBar position="fixed">
98+
<Container maxWidth="xl">
99+
<Toolbar>
100+
<IconButton
101+
color="inherit"
102+
aria-label="open drawer"
103+
onClick={handleDrawerOpen}
104+
edge="start"
105+
sx={{ mr: 2, ...(open && { display: 'none' }) }}
106+
>
107+
<MenuIcon />
108+
</IconButton>
109+
<Box sx={{ flexGrow: 1 }}></Box>
110+
<Box>
111+
{solanaAddress ? (
112+
<TextField
113+
size="small"
114+
label="Solana Address"
115+
value={solanaAddress}
116+
spellCheck={false}
117+
InputProps={{
118+
sx: {
119+
color: 'white',
120+
},
121+
disableUnderline: true,
122+
endAdornment: (
123+
<InputAdornment position="end">
124+
<IconButton
125+
size="small"
126+
onClick={() =>
127+
navigator.clipboard.writeText(solanaAddress)
128+
}
129+
edge="end"
130+
>
131+
<ContentCopy fontSize="small" />
132+
</IconButton>
133+
</InputAdornment>
134+
),
135+
}}
136+
/>
137+
) : (
138+
<Button
139+
color="inherit"
140+
variant="outlined"
141+
onClick={async () => await generateSolanaAddress()}
142+
endIcon={generatingSolanaAddress ? <Pending /> : <Send />}
143+
>
144+
Get Solana Wallet
145+
</Button>
146+
)}
147+
</Box>
148+
</Toolbar>
149+
</Container>
150+
</MuiAppBar>
151+
<Drawer
152+
sx={{
153+
width: DRAWER_WIDTH,
154+
flexShrink: 0,
155+
'& .MuiDrawer-paper': {
156+
width: DRAWER_WIDTH,
157+
boxSizing: 'border-box',
158+
},
159+
}}
160+
variant="persistent"
161+
anchor="left"
162+
open={open}
163+
>
164+
<DrawerHeader>
165+
<IconButton onClick={handleDrawerClose}>
166+
{theme.direction === 'ltr' ? (
167+
<ChevronLeftIcon />
168+
) : (
169+
<ChevronRightIcon />
170+
)}
171+
</IconButton>
172+
</DrawerHeader>
173+
<Divider />
174+
<List>
175+
{DRAWER_ITEMS.map((item, index) => (
176+
<ListItem key={index} disablePadding>
177+
<ListItemButton onClick={() => router.push(item.link)}>
178+
<ListItemText primary={item.name} />
179+
</ListItemButton>
180+
</ListItem>
181+
))}
182+
</List>
183+
</Drawer>
184+
<Container maxWidth="xl">
185+
<DrawerHeader />
186+
{children}
187+
</Container>
188+
</Container>
189+
</Box>
190+
);
191+
}

‎frontend/eslint.config.mjs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import globals from 'globals';
2+
import pluginJs from '@eslint/js';
3+
import tseslint from 'typescript-eslint';
4+
import pluginReact from 'eslint-plugin-react';
5+
6+
export default [
7+
{ ignores: ['.next/', '*.mjs'] },
8+
{ files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] },
9+
{ languageOptions: { globals: globals.browser } },
10+
pluginJs.configs.recommended,
11+
...tseslint.configs.recommended,
12+
pluginReact.configs.flat.recommended,
13+
];

‎frontend/next-env.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/basic-features/typescript for more information.

‎frontend/next.config.mjs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
env: {
4+
portalClientApiKey: 'bc1f92d2-82e8-457d-97ee-581c0bb6106e',
5+
solanaChainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
6+
solMint: 'So11111111111111111111111111111111111111112',
7+
pyUsdMint: 'CXk2AMBfi3TwaEL2468s6zP8xq9NxTXjp9gjMgzeUynM',
8+
solanaRpcUrl: 'https://api.devnet.solana.com',
9+
},
10+
};
11+
12+
export default nextConfig;

0 commit comments

Comments
 (0)