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

wip add metamask connection #4

Merged
merged 2 commits into from
May 26, 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
Binary file modified frontend/.yarn/install-state.gz
Binary file not shown.
52 changes: 28 additions & 24 deletions frontend/contracts/src/scripts/api.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,75 @@
import {
import {
createPublicClient,
createWalletClient,
custom,
http,
parseAbiItem,
encodeFunctionData } from 'viem'
encodeFunctionData,
WalletClient} from 'viem'
import { sepolia } from 'viem/chains'

import abi from '../../lib/PoEMarketplace_abi.json'

const CONTRACT = process.env.CONTRACT_ADDRESS || '0x..'
const CONTRACT = '0x7C3c3cEFAde338Bb4461d365ed5B1955A944F2cD'

const publicClient = createPublicClient({
const publicClient = createPublicClient({
chain: sepolia,
transport: http()
transport: custom((window as any).ethereum)
})

const client = createWalletClient({
chain: sepolia,
transport: custom((window as any).ethereum)
})

export const [account] = await client.getAddresses()
export const walletClient = createWalletClient({
chain: sepolia,
transport: custom((window as any).ethereum)
})


// generic function to write to the contract and return tx hash
const writeContract = async (functionName:string, args: any[]) => {

const { request } = await publicClient.simulateContract({
const writeContract = async (walletClient: WalletClient, functionName: string, args: any[]) => {
const [account] = await walletClient.getAddresses()
const {request} = await publicClient.simulateContract({
account,
address: CONTRACT as `0x${string}`,
abi: abi,
functionName: functionName,
args: args
})
return await client.writeContract(request)
return await walletClient.writeContract(request)
}

// watches for the TokenPurchased event emitted during the purchaseToken function call.
// This will be used to trigger the redeem function call.
export const unwatchPurchase = publicClient.watchEvent({
address: CONTRACT as `0x${string}`,
event: parseAbiItem('event TokenPurchased(uint256 indexed id, address indexed buyer)'),
// TO DO - call function with logs to derive public key https://viem.sh/docs/utilities/recoverPublicKey#recoverpublickey
onLogs: logs => console.log(logs)
address: CONTRACT as `0x${string}`,
event: parseAbiItem('event TokenPurchased(uint256 indexed id, address indexed buyer)'),
// TO DO - call function with logs to derive public key https://viem.sh/docs/utilities/recoverPublicKey#recoverpublickey
onLogs: logs => console.log(logs)
})

// watches for the TokenPurchased event emitted during the purchaseToken function call.
// This will be used to trigger the redeem function call.
export const unwatchRedeem = publicClient.watchEvent({
address: CONTRACT as `0x${string}`,
event: parseAbiItem('event ExploitRedeemed(uint256 indexed id, address indexed buyer)'),
event: parseAbiItem('event ExploitRedeemed(uint256 indexed id, address indexed buyer)'),
// TO DO - call function with logs to derive public key https://viem.sh/docs/utilities/recoverPublicKey#recoverpublickey
onLogs: logs => console.log(logs)
})
onLogs: logs => console.log(logs)
})


// A White Hat Hacker can post an exploit to the marketplace
export const postExploit = async (description:string, price:bigint, hash:bigint) => await writeContract('postExploit', [description, price, hash])
export const postExploit = async (walletClient: WalletClient, description: string, price: bigint, hash: bigint) => await writeContract(walletClient, 'postExploit', [description, price, hash])

// A vendor can purchase the exploit token from the marketplace
export const purchaseToken = async (tokenId:number) => await writeContract('purchaseToken', [tokenId])
export const purchaseToken = async (walletClient: WalletClient, tokenId: number) => await writeContract(walletClient, 'purchaseToken', [tokenId])

// The White Hat Hacker uses the vendor's public key derived from the purchaseToken transaction to compute the proofs and receive the payment
//export const redeem = async (tokenId:number, key:bigint) => await writeContract('redeemExploit', [tokenId, key])
//export const redeem = async (walletClient: WalletClient, tokenId: number, key: bigint) => await writeContract(walletClient, 'redeemExploit', [tokenId, key])

// Finally, the vendor can retrieve the shared key from the exploit token
export const retrieveSharedKeyCipher = async (tokenId:number) => publicClient.readContract({
export const retrieveSharedKeyCipher = async (tokenId: number) => publicClient.readContract({
address: CONTRACT as `0x${string}`,
abi: abi,
functionName: 'getExploitKey',
Expand All @@ -83,6 +87,7 @@ export const redeemSigned = async (tokenId:number, key:bigint) => {
args: [tokenId, key]
})

const [account] = await walletClient.getAddresses()
const request = await client.prepareTransactionRequest({
account,
to: CONTRACT as `0x${string}`,
Expand All @@ -94,5 +99,4 @@ export const redeemSigned = async (tokenId:number, key:bigint) => {
const txHash = await client.sendRawTransaction(signature as any)

return { txHash, signature }

}
23 changes: 12 additions & 11 deletions frontend/contracts/src/scripts/getContractState.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import dotenv from 'dotenv'
import { GetStorageAtReturnType, createPublicClient, http, toHex } from 'viem'
import { sepolia } from 'viem/chains'
import fs from 'fs'
import {GetStorageAtReturnType, createPublicClient, toHex, custom} from 'viem'
import {sepolia} from 'viem/chains'
import fs from 'vite-plugin-fs/browser';

const publicClient = createPublicClient({
const publicClient = createPublicClient({
chain: sepolia,
transport: http()
transport: custom(window.ethereum)
})

async function getStorageAtSlot(target:string, slot:number) {
async function getStorageAtSlot(target: string, slot: number) {
console.log(target)
console.log(slot)
return await publicClient.getStorageAt({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
slot: toHex(0)
})
}

async function fetchAndStoreContractState(target:string) {
async function fetchAndStoreContractState(target: string) {
const slots = 100; // Number of storage slots to fetch. Adjust as needed.
const state: GetStorageAtReturnType[] = [];

for (let slot = 0; slot < slots; slot++) {
const storage:GetStorageAtReturnType = await getStorageAtSlot(target, slot)
const storage: GetStorageAtReturnType = await getStorageAtSlot(target, slot)
if (storage) {
state.push(storage);
}
}

// Store the state in a JSON file
fs.writeFileSync('contractState.json', JSON.stringify(state));
await fs.writeFile('contractState.json', JSON.stringify(state));

console.log('Contract state saved.');
}

Expand Down
24 changes: 24 additions & 0 deletions frontend/contracts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
}
5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.13",
"@metamask/providers": "^16.1.0",
"@mui/icons-material": "^5.15.18",
"@mui/material": "^5.15.18",
"@types/crypto-js": "^4.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
"ts-chacha20": "^1.2.0"
"ts-chacha20": "^1.2.0",
"vite-plugin-fs": "^1.1.0"
},
"devDependencies": {
"@types/node": "^20.12.12",
Expand All @@ -32,6 +34,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"viem": "^2.12.1",
"vite": "^5.2.0"
},
"packageManager": "[email protected]"
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ main {
.read-the-docs {
color: #888;
}

a {
color: white;
}
1 change: 0 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import './App.css'

import React from 'react';
import {Outlet} from 'react-router-dom';
import Header from './components/Header';
Expand Down
26 changes: 21 additions & 5 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,36 @@ import React from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import { Link as RouterLink } from 'react-router-dom';
import {Link as RouterLink} from 'react-router-dom';
import Link, {LinkProps} from '@mui/material/Link';
import styled from '@emotion/styled'
import Button from "@mui/material/Button";
import useMetaMask from "../hooks/useMetamask.ts";

const Header: React.FC = () => {
const {isConnected, connectMetaMask} = useMetaMask();

return (
<StyledAppBar position="static" sx={{ width: '100%' }} >
<StyledAppBar position="static" sx={{width: '100%'}}>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
My Web3 App
</Typography>
<StyledLink component={RouterLink} to="/" color="inherit" underline="none" sx={{ marginRight: 2 }}>
<StyledLink component={RouterLink} to="/" color="inherit" underline="none" sx={{marginRight: 2}}>
Home
</StyledLink>
<StyledLink component={RouterLink} to="/issue" color="inherit" underline="none" sx={{ marginRight: 2 }}>
<StyledLink component={RouterLink} to="/issue" color="inherit" underline="none" sx={{marginRight: 2}}>
Issue
</StyledLink>
<StyledLink component={RouterLink} to="/hacker-info" color="inherit" underline="none">
Hacker info
</StyledLink>
{!isConnected && (
<StyledButton onClick={() => connectMetaMask()} type="button" variant="contained"
size="medium" color="primary">
Connect MetaMask
</StyledButton>
)}
</Toolbar>
</StyledAppBar>
);
Expand All @@ -41,5 +51,11 @@ const StyledLink = styled(Link)<LinkProps & { component: React.ElementType, to?:
text-transform: uppercase;
`;

const StyledButton = styled(Button)`
padding-left: 4px;
padding-right: 4px;
margin-left: 20px;
`

export default Header;

71 changes: 71 additions & 0 deletions frontend/src/hooks/useMetamask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState, useEffect } from 'react';
import {createPublicClient, createWalletClient, custom} from 'viem';
import { sepolia } from 'viem/chains';
import { MetaMaskInpageProvider } from "@metamask/providers";

declare global {
interface Window{
ethereum?:MetaMaskInpageProvider
}
}

const useMetaMask = () => {
const [isConnected, setIsConnected] = useState(false);
const [account, setAccount] = useState(null);
const [walletClient, setWalletClient] = useState(null);
const [client, setClient] = useState(null);

const connectMetaMask = async () => {
if (window.ethereum) {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
const walletClient = createWalletClient({
transport: custom(window.ethereum),
chain: sepolia,
});

const accounts = await walletClient.request({ method: 'eth_accounts' }) as string[];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setWalletClient(walletClient)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setAccount(accounts[0]);
setIsConnected(true);
} catch (error) {
console.error("User denied account access or there was an error", error);
}
} else {
console.error("MetaMask is not installed");
}
};

useEffect(() => {
const checkConnection = async () => {
if (window.ethereum) {
const publicClient = createPublicClient({
chain: sepolia,
transport: custom(window.ethereum),
});

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const accounts = await publicClient.request({ method: 'eth_accounts' }) as string[];
if (accounts.length > 0) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setClient(publicClient);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setAccount(accounts[0]);
setIsConnected(true);
}
}
};
checkConnection();
}, []);

return { isConnected, account, connectMetaMask, walletClient, client };
};

export default useMetaMask;
12 changes: 8 additions & 4 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import {CssBaseline} from '@mui/material';
import {ThemeProvider, createTheme} from '@mui/material/styles';
import {router} from './router.tsx';

// #D63A2B - red
// #01518B - blue
// #1AA52C - green

const theme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#1976d2',
main: '#01518B',
},
secondary: {
main: '#dc004e',
main: '#D63A2B',
},
},
});
Expand All @@ -28,8 +32,8 @@ const root = createRoot(container!);
root.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<RouterProvider router={router}/>
<CssBaseline/>
<RouterProvider router={router}/>
</ThemeProvider>
</React.StrictMode>
);
Expand Down
Loading
Loading