From b52f799728c987ea6958ede3c70ce4470a7301b1 Mon Sep 17 00:00:00 2001 From: yagyanssh Date: Sun, 20 Oct 2024 02:14:26 +0530 Subject: [PATCH 1/3] added withdraw functionality --- package-lock.json | 121 +++++++++++ src/components/WalletPage/WalletDetail.tsx | 5 +- src/components/WalletPage/actions.ts | 3 +- .../WalletPage/withdraw/withdraw.tsx | 197 ++++++++++++++++++ src/services/walletService.ts | 57 ++++- 5 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 src/components/WalletPage/withdraw/withdraw.tsx diff --git a/package-lock.json b/package-lock.json index be12af4..c432ffe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10037,6 +10037,7 @@ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1691.0.tgz", "integrity": "sha512-/F2YC+DlsY3UBM2Bdnh5RLHOPNibS/+IcjUuhP8XuctyrN+MlL+fWDAiela32LTDk7hMy4rx8MTgvbJ+0blO5g==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -21296,6 +21297,126 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/src/components/WalletPage/WalletDetail.tsx b/src/components/WalletPage/WalletDetail.tsx index 82a43c8..393b13b 100644 --- a/src/components/WalletPage/WalletDetail.tsx +++ b/src/components/WalletPage/WalletDetail.tsx @@ -4,6 +4,8 @@ import { actions, ActionType } from './actions' import { ReceiveQR } from './ReceiveQR' import TokenSwap from './Swap/Swap' import SendToken from './Send/Send' +import WithdrawalComponent from './withdraw/withdraw' + export interface WalletDetailProps { wallet?: string @@ -46,7 +48,7 @@ const WalletDetail = ({ wallet, usdbalance , solbalance}: WalletDetailProps) => )} - {(currentAction !== 'send' && currentAction !== 'swap' ) && + {(currentAction !== 'send' && currentAction !== 'swap' && currentAction !== 'withdraw' ) &&
@@ -96,6 +98,7 @@ const WalletDetail = ({ wallet, usdbalance , solbalance}: WalletDetailProps) => {currentAction === 'receive' && } {currentAction === 'swap' && } {currentAction === 'send' && } + {currentAction === 'withdraw' && }
) diff --git a/src/components/WalletPage/actions.ts b/src/components/WalletPage/actions.ts index a304264..2663f82 100644 --- a/src/components/WalletPage/actions.ts +++ b/src/components/WalletPage/actions.ts @@ -1,4 +1,4 @@ -import { Send, Download, CreditCard, ArrowDownUp } from 'lucide-react'; + import { Send, Download, CreditCard, ArrowDownUp, WalletCards } from 'lucide-react'; //different component for actions export const actions = [ @@ -6,6 +6,7 @@ export const actions = [ { icon: Download, label: 'Receive', type: 'receive' as const }, { icon: CreditCard, label: 'Buy', type: 'buy' as const }, { icon: ArrowDownUp, label: 'Swap', type: 'swap' as const }, + { icon: WalletCards, label: 'Withdraw', type: 'withdraw' as const } ]; //defined type of actions diff --git a/src/components/WalletPage/withdraw/withdraw.tsx b/src/components/WalletPage/withdraw/withdraw.tsx new file mode 100644 index 0000000..7076db0 --- /dev/null +++ b/src/components/WalletPage/withdraw/withdraw.tsx @@ -0,0 +1,197 @@ +import { ActionType } from '../actions' +import React, { useState } from 'react' +import { useWallet } from '@solana/wallet-adapter-react' +import { withdrawSol } from '@/services/walletService' +import { ChevronRight, Wallet } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { PublicKey } from '@solana/web3.js' +import { useConnection } from '@solana/wallet-adapter-react' + + +// WithdrawalOption Component (to choose the withdrawal method) +interface WithdrawalOptionProps { + icon: React.ReactNode + title: string + description: string + onClick: () => void +} + +const WithdrawalOption: React.FC = ({ + icon, + title, + description, + onClick, +}) => ( + +) + +// WalletWithdrawalForm Component (to handle wallet withdrawal form) +interface WalletWithdrawalFormProps { + onCancel: () => void +} + +const WalletWithdrawalForm: React.FC = ({ + onCancel, +}) => { + const [amount, setAmount] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const { publicKey } = useWallet() + const { connection } = useConnection() + + const handleWithdraw = async () => { + if (!publicKey) { + setError('Wallet not connected') + return + } + const amountNum = parseFloat(amount) + if (isNaN(amountNum) || amountNum <= 0) { + setError('Please enter a vaild amount') + return + } + setIsLoading(true) + setError(null) + + try { + const signature = await withdrawSol(publicKey, amountNum) + console.log('Withraw Successful:', signature) + // TODO a success message for the user - implement before raising PR + } catch (error) { + console.log('Withdrawal Failed:', error) + setError('Withdrawl failed. Please try again.') + } finally { + setIsLoading(false) + } + } + + return ( +
+

Withdraw to Wallet

+
+ + +
+
+ +
+ setAmount(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-md text-xl text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + +
+
+
+ {error &&

{error}

} +
+ + +
+
+ ) +} + +// Main Component +type WithdrawalOption = 'none' | 'wallet' | 'bank' | 'gift' + +interface WithdrawalComponentProps { + setCurrent: (action: ActionType | null) => void +} + +const WithdrawalComponent: React.FC = ({ + setCurrent, +}) => { + const [selectedOption, setSelectedOption] = useState('none') + + const renderContent = () => { + switch (selectedOption) { + case 'wallet': + return ( + setSelectedOption('none')} /> + ) + case 'none': + default: + return ( + <> +

Withdraw

+

+ Select destination for withdrawal: +

+ + 🏦} + title="To Bank Account" + description="Withdraw to a bank account you specify (US and Europe only)." + onClick={() => setSelectedOption('bank')} + /> + + 🎁} + title="Gift Card" + description="Buy gift cards from various brands with crypto!" + onClick={() => setSelectedOption('gift')} + /> + + } + title="To Connected Wallet" + description="Assets will be sent to the connected wallet." + onClick={() => setSelectedOption('wallet')} + /> + + + ) + } + } + + return ( +
+ {renderContent()} +
+ ) +} + +export default WithdrawalComponent diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 4eada0d..bda2394 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -1,10 +1,19 @@ 'use server' -import { Keypair, LAMPORTS_PER_SOL, Transaction } from '@solana/web3.js' +import { + Connection, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + sendAndConfirmTransaction, + SystemProgram, + Transaction, +} from '@solana/web3.js' import prisma from '@/db' import { User } from '@prisma/client' import axios from 'axios' import { pvtKeyEncryptionManager } from '@/actions/pvtKeyEncryptMgmt' import base58 from 'bs58' +import { pvtKeyDecryptionManager } from '@/actions/pvtKeyDecryptMgmt' const customRpcUrl = process.env.NEXT_PUBLIC_SOLANA_RPC || '' const coinGeckoUrl = process.env.NEXT_PUBLIC_COIN_GECKO_API || '' @@ -65,14 +74,14 @@ export async function convertSoltoUSD(sol: number) { } try { - const res = await axios.get(coinGeckoUrl,{ - params:{ - 'ids': 'solana', - 'vs_currencies': 'usd' - } + const res = await axios.get(coinGeckoUrl, { + params: { + ids: 'solana', + vs_currencies: 'usd', + }, }) const price = res?.data?.solana?.usd - return (sol*price).toFixed(2); + return (sol * price).toFixed(2) } catch (error) { console.error('Failed to fetch price:', error) } @@ -94,8 +103,40 @@ export async function fetchUserBalance(publicKey?: string) { const balance = res?.data?.result?.value const solBalance = balance / LAMPORTS_PER_SOL const usdBalance = await convertSoltoUSD(solBalance) - return {usdBalance,solBalance}; + return { usdBalance, solBalance } } catch (error) { console.error('Failed to fetch balance:', error) } } + +export async function withdrawSol( + nativeWalPubKey: PublicKey, + amount: number +) { + const connection = new Connection(customRpcUrl, 'confirmed'); + try { + const decryptedPvtKey = await pvtKeyDecryptionManager(); + const secretKeyUint8Array = base58.decode(decryptedPvtKey); + const keypair = Keypair.fromSecretKey(secretKeyUint8Array); + + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: keypair.publicKey, + toPubkey: nativeWalPubKey, + lamports: amount * LAMPORTS_PER_SOL, + }), + ); + const { blockhash } = await connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = keypair.publicKey; + + transaction.sign(keypair); + + const signature = await sendAndConfirmTransaction(connection, transaction, [keypair]); + console.log('Transaction signature:', signature); + return signature; + } catch (error) { + console.error('Error in withdrawSol function:', error); + throw error; + } +} \ No newline at end of file From e107826cdf7ad70c32cb04203e123927e1a88514 Mon Sep 17 00:00:00 2001 From: yagyanssh Date: Sun, 20 Oct 2024 04:53:23 +0530 Subject: [PATCH 2/3] Fixed Invalid keylenght er --- src/services/aes-module.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/aes-module.ts b/src/services/aes-module.ts index baf2f50..319097e 100644 --- a/src/services/aes-module.ts +++ b/src/services/aes-module.ts @@ -31,10 +31,11 @@ export function aesDecrypt(encryptedData: any) { const textParts = encryptedData.split(':') const iv = Buffer.from(textParts.shift(), 'hex') const encryptedText = Buffer.from(textParts.join(':'), 'hex') + const encryptionKey32 = crypto.createHash('sha256').update(Buffer.from(ENCRYPTION_KEY, 'hex')).digest(); const decipher = crypto.createDecipheriv( - 'aes-256-cbc', - Buffer.from(ENCRYPTION_KEY), + 'aes-256-ctr', + encryptionKey32, iv, ) From 545d22eda50e30fd3f92f7fff255945f20c48aa0 Mon Sep 17 00:00:00 2001 From: yagyanssh Date: Sun, 20 Oct 2024 05:38:52 +0530 Subject: [PATCH 3/3] Added WalletmultiButton throughout Pages --- src/components/Appbar/Appbar.tsx | 74 +++++++++++-------- src/components/WalletPage/TopBar.tsx | 29 ++++++-- .../WalletPage/withdraw/withdraw.tsx | 11 ++- 3 files changed, 69 insertions(+), 45 deletions(-) diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx index 8faa656..d02c8bc 100644 --- a/src/components/Appbar/Appbar.tsx +++ b/src/components/Appbar/Appbar.tsx @@ -50,49 +50,51 @@ const Appbar = () => { if (!isMounted) return null return ( -
+
-
+
-
- +
+
{data && ( - + )} {data && data?.user ? ( {!data?.user.image ? ( -
+
) : ( @@ -100,11 +102,15 @@ const Appbar = () => { )} - +
{!data?.user.image ? ( -
+
) : ( @@ -113,7 +119,11 @@ const Appbar = () => {
- {data?.user?.name} + + {data?.user?.name} + {data?.user?.email} @@ -157,4 +167,4 @@ const Appbar = () => { ) } -export default Appbar \ No newline at end of file +export default Appbar diff --git a/src/components/WalletPage/TopBar.tsx b/src/components/WalletPage/TopBar.tsx index 578f0ce..20445db 100644 --- a/src/components/WalletPage/TopBar.tsx +++ b/src/components/WalletPage/TopBar.tsx @@ -7,10 +7,13 @@ import { useState } from 'react' import LeftSideBar from './LeftSideBar' import ProfileDropDown from '../common/ProfileDropDown' import React from 'react' +import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' +import { useTheme } from 'next-themes' const TopBar = () => { const { data } = useSession() const [open, setOpen] = useState(false) + const { theme } = useTheme() return (
@@ -33,14 +36,26 @@ const TopBar = () => {
- - + + +
+
+ {data && data.user ? ( + <> + + + + ) : ( +
+
+
+ )}
- {data && data?.user ? : ( -
-
-
- )}
) } diff --git a/src/components/WalletPage/withdraw/withdraw.tsx b/src/components/WalletPage/withdraw/withdraw.tsx index 7076db0..844e715 100644 --- a/src/components/WalletPage/withdraw/withdraw.tsx +++ b/src/components/WalletPage/withdraw/withdraw.tsx @@ -4,11 +4,9 @@ import { useWallet } from '@solana/wallet-adapter-react' import { withdrawSol } from '@/services/walletService' import { ChevronRight, Wallet } from 'lucide-react' import { Button } from '@/components/ui/button' -import { PublicKey } from '@solana/web3.js' import { useConnection } from '@solana/wallet-adapter-react' -// WithdrawalOption Component (to choose the withdrawal method) interface WithdrawalOptionProps { icon: React.ReactNode title: string @@ -37,7 +35,6 @@ const WithdrawalOption: React.FC = ({ ) -// WalletWithdrawalForm Component (to handle wallet withdrawal form) interface WalletWithdrawalFormProps { onCancel: () => void } @@ -48,8 +45,9 @@ const WalletWithdrawalForm: React.FC = ({ const [amount, setAmount] = useState('') const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) + const [success, setSuccess] = useState(null); const { publicKey } = useWallet() - const { connection } = useConnection() + // const { connection } = useConnection() const handleWithdraw = async () => { if (!publicKey) { @@ -63,11 +61,12 @@ const WalletWithdrawalForm: React.FC = ({ } setIsLoading(true) setError(null) + setSuccess(null) try { const signature = await withdrawSol(publicKey, amountNum) console.log('Withraw Successful:', signature) - // TODO a success message for the user - implement before raising PR + setSuccess(`Withdrawal successful! Transaction Signature: ${signature}`); } catch (error) { console.log('Withdrawal Failed:', error) setError('Withdrawl failed. Please try again.') @@ -92,7 +91,7 @@ const WalletWithdrawalForm: React.FC = ({
setAmount(e.target.value)} className="w-full px-4 py-3 border border-gray-300 rounded-md text-xl text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"