Skip to content

Commit

Permalink
feat(updates): check for keyholder
Browse files Browse the repository at this point in the history
  • Loading branch information
Aman-zishan committed Dec 3, 2023
1 parent cfe1e0a commit bbe282b
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 58 deletions.
Binary file added assets/key.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@hirosystems/clarinet-sdk": "^1.1.0",
"@micro-stacks/react": "^1.0.9",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
Expand Down
55 changes: 10 additions & 45 deletions src/components/connectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,58 +21,23 @@ import { ExternalLink } from '../external-link';
import { ArrowRight } from 'lucide-react';
import { truncateAddress, fetchSTXBalance } from '../lib/utils';
import { SVGComponent } from './stacksSvg';
import useConnect from '@/lib/hooks/useConnect';

function ConnectWallet(): ReactElement {
const {
user,
connectWallet,
disconnectWallet,
network,
userSession,
balance
} = useConnect();
const [address, setAddress] = useState('');
const [balance, setBalance] = useState(0);

const [isSignatureVerified, setIsSignatureVerified] = useState(false);
const [hasFetchedReadOnly, setHasFetchedReadOnly] = useState(false);

// Initialize your app configuration and user session here
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });

const message = 'Hello, Hiro Hacks!';
const network = new StacksTestnet();

// Define your authentication options here
const authOptions = {
userSession,
appDetails: {
name: 'My App',
icon: 'src/favicon.svg'
},
onFinish: async (data: FinishedAuthData) => {
// Handle successful authentication here
const userData = data.userSession.loadUserData();
console.log(userData);
setAddress(userData.profile.stxAddress.testnet); // or .testnet for testnet
const fetchedSTXBalance = await fetchSTXBalance(
userData.profile.stxAddress.testnet
)
.then((data) => {
console.log(data);
setBalance(data.balance / 1000000);
})
.catch((error) => console.error(error));
console.log('BALANCE', fetchedSTXBalance);
},
onCancel: () => {
// Handle authentication cancellation here
},
redirectTo: '/'
};

const connectWallet = () => {
showConnect(authOptions);
};

const disconnectWallet = () => {
if (userSession.isUserSignedIn()) {
userSession.signUserOut('/home');
setAddress('');
}
};

const fetchReadOnly = async (senderAddress: string) => {
// Define your contract details here
Expand Down
177 changes: 177 additions & 0 deletions src/lib/hooks/useConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {
AppConfig,
FinishedAuthData,
UserData,
UserSession,
openSignatureRequestPopup,
showConnect
} from '@stacks/connect';
import { verifyMessageSignatureRsv } from '@stacks/encryption';
import { StacksDevnet } from '@stacks/network';
import {
callReadOnlyFunction,
cvToValue,
getAddressFromPublicKey,
standardPrincipalCV
} from '@stacks/transactions';
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchSTXBalance } from '../utils';

const initialValue = {
email: '',
decentralizedID: '',
identityAddress: '',
appPrivateKey: '',
hubUrl: '',
coreNode: '',
authResponseToken: '',
coreSessionToken: '',
gaiaAssociationToken: '',
profile: '',
gaiaHubConfig: '',
appPrivateKeyFromWalletSalt: ''
};
const userWalletAtom = atomWithStorage<UserData>('userWallet', initialValue);

// Initialize your app configuration and user session here
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });

const message = 'Check if i am a keyholder ;)';
const network = new StacksDevnet();

// Define your authentication options here

function useConnect() {
const navigate = useNavigate();
const [user, setUser] = useAtom(userWalletAtom);
const [balance, setBalance] = useState(0);

const senderAddress = userSession.loadUserData().profile.stxAddress.testnet;
const contractAddress = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM';
const contractName = 'cooperative-orange-gamefowl';
const functionName = 'is-keyholder';

async function checkIsKeyHolder(principal: string) {
const functionArgs = [
standardPrincipalCV(principal),
standardPrincipalCV(principal)
];
const result = await callReadOnlyFunction({
network,
contractAddress,
contractName,
functionName,
functionArgs,
senderAddress
});

console.log('Result:', cvToValue(result));

return cvToValue(result);
}

async function handleInitialLogin() {
if (userSession.isUserSignedIn()) {
await openSignatureRequestPopup({
message,
network,
onFinish: async ({ publicKey, signature }) => {
const verified = verifyMessageSignatureRsv({
message,
publicKey,
signature
});
if (verified) {
// The signature is verified, so now we can check if the user is a keyholder

const address = getAddressFromPublicKey(publicKey, network.version);
const isKeyHolder = await checkIsKeyHolder(address);
if (isKeyHolder) {
console.log('The user is a keyholder');
navigate('/home');
// The user is a keyholder, so they are authorized to access the chatroom
} else {
console.log('The user is not a keyholder');
navigate('/buy-first-key');
// The user is not a keyholder, so they are not authorized to access the chatroom
}
}
}
});
}
}

const authOptions = {
userSession,
appDetails: {
name: 'My App',
icon: 'src/favicon.svg'
},
onFinish: async (data: FinishedAuthData) => {
// Handle successful authentication here
const userData = data.userSession.loadUserData();
console.log(userData);
setUser(userData); // or .testnet for testnet
const fetchedSTXBalance = await fetchSTXBalance(
userData.profile.stxAddress.testnet
)
.then((data) => {
console.log(data);
setBalance(data.balance / 1000000);
handleInitialLogin();
})
.catch((error) => console.error(error));
console.log('BALANCE', fetchedSTXBalance);
},
onCancel: () => {
// Handle authentication cancellation here
}
};

const fetchBalance = async () => {
if (userSession.isUserSignedIn()) {
const userData = userSession.loadUserData();
try {
const fetchedSTXBalance = await fetchSTXBalance(
userData.profile.stxAddress.testnet
);
setBalance(fetchedSTXBalance.balance / 1000000);
console.log('Fetched balance:', fetchedSTXBalance);
} catch (error) {
console.error('Failed to fetch balance:', error);
}
}
};

React.useEffect(() => {
fetchBalance();
}, []); // The empty array causes this effect to only run on mount

const connectWallet = () => {
showConnect(authOptions);
};

const disconnectWallet = () => {
if (userSession.isUserSignedIn()) {
userSession.signUserOut('/home');
setUser(initialValue);
}
};

return {
connectWallet,
disconnectWallet,
user,
balance,
network,
userSession,
contractAddress,
contractName
};
}

export default useConnect;
33 changes: 22 additions & 11 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@ import React from 'react';
import App from './App';
import { BrowserRouter, Link, Route, Routes } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
import * as MicroStacks from '@micro-stacks/react';

import './index.css';
import Home from './pages/Home';
import Profile from './pages/Profile';
import Explore from './pages/Explore';
import Chats from './pages/Chats';
import Login from './pages/Login';
import BuyFirstKey from './pages/BuyFirstKey';

createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/home" element={<Home />} />
<Route path="/chats" element={<Chats />} />
<Route path="/profile" element={<Profile />} />
<Route path="/explore" element={<Explore />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
<MicroStacks.ClientProvider
appName="sFriend.tech"
appIconUrl="APP_ICON.png"
network="testnet"
>
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/login" element={<Login />} />
<Route path="/buy-first-key" element={<BuyFirstKey />} />
<Route path="/home" element={<Home />} />
<Route path="/chats" element={<Chats />} />
<Route path="/profile" element={<Profile />} />
<Route path="/explore" element={<Explore />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
</MicroStacks.ClientProvider>
);
79 changes: 79 additions & 0 deletions src/pages/BuyFirstKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import useConnect from '@/lib/hooks/useConnect';
import { useOpenContractCall } from '@micro-stacks/react';
import { standardPrincipalCV, uintCV } from 'micro-stacks/clarity';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const BuyFirstKey: React.FC = () => {
const navigate = useNavigate();
const { userSession, contractAddress, contractName } = useConnect();
const { openContractCall, isRequestPending } = useOpenContractCall();

const [response, setResponse] = useState(null);

const functionArgs = [
standardPrincipalCV(
userSession?.loadUserData().profile?.stxAddress.testnet
),
uintCV(1)
];

const handleOpenContractCall = async () => {
await openContractCall({
contractAddress: contractAddress,
contractName: contractName,
functionName: 'buy-keys',
functionArgs,
postConditions: [],

onFinish: async (data: any) => {
console.log('finished contract call!', data);
setResponse(data);
navigate('/home');
},
onCancel: () => {
console.log('popup closed!');
}
});
};
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-white w-screen">
<div className="text-center">
<div className="mb-0">
<div className="px-4 py-4 text-center flex flex-row items-center justify-center gap-2">
<img src="assets/key.png" alt="" width={200} />
</div>
</div>
<h1 className="text-3xl font-bold mb-6">Buy your first key</h1>

<p className="text-gray-600 text-lg mb-8 w-[700px]">
Everyone of sFriend.tech has a chat unlocked by their keys.These keys
can be bought and sold on a person's profile and their price goes up
and down based on how many are circulating.
</p>

<p className="text-gray-600 text-lg mb-8 w-[700px]">
You'll earn trading fee everytime your keys are bought and sold by
anyone
</p>

<p className="text-gray-600 text-lg mb-8 w-[700px]">
To create your profile, buy the first key to buy your own room for
free!
</p>
<button
onClick={() => {
if (userSession?.isUserSignedIn()) {
handleOpenContractCall();
}
}}
className="bg-blue-500 text-white font-bold py-2 px-4 rounded-full mb-4 hover:bg-blue-600"
>
{isRequestPending ? 'request pending...' : 'Buy Key for $0'}
</button>
</div>
</div>
);
};

export default BuyFirstKey;
1 change: 0 additions & 1 deletion src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ConnectWallet from '@/components/connectWallet';
import LeftMenu from '@/components/leftMenu';
import React from 'react';

const Home = () => {
return (
Expand Down
Loading

0 comments on commit bbe282b

Please sign in to comment.