This commit is contained in:
zramsay 2025-01-02 17:15:21 -05:00
parent af601f4bbf
commit 14d278510c
5 changed files with 150 additions and 70 deletions

View File

@ -5,53 +5,33 @@ import WalletHeader from '../components/WalletHeader'
import AIServiceCard from '../components/AIServiceCard' import AIServiceCard from '../components/AIServiceCard'
import { generateChatResponse, ChatGenerationResult, CHAT_MODELS } from '../services/chatService' import { generateChatResponse, ChatGenerationResult, CHAT_MODELS } from '../services/chatService'
import { processMTMPayment } from '../services/paymentService' import { processMTMPayment } from '../services/paymentService'
import { connectWallet, WalletState } from '../services/walletService'
interface WalletState { import { WalletType } from '../services/types'
connected: boolean
publicKey: string | null
}
declare global {
interface Window {
solflare: any;
}
}
const Page: React.FC = (): React.ReactElement => { const Page: React.FC = (): React.ReactElement => {
const [walletState, setWalletState] = useState<WalletState>({ const [walletState, setWalletState] = useState<WalletState>({
connected: false, connected: false,
publicKey: null, publicKey: null,
type: null
}) })
const connectWallet = async (): Promise<void> => { const handleConnect = async (walletType: WalletType): Promise<void> => {
try { try {
if (typeof window === 'undefined' || !window.solflare) { const newWalletState = await connectWallet(walletType)
throw new Error('Solflare wallet not found! Please install it first.') setWalletState(newWalletState)
}
await window.solflare.connect()
if (!window.solflare.publicKey) {
throw new Error('Failed to connect to wallet')
}
const publicKey: string = window.solflare.publicKey.toString()
setWalletState({
connected: true,
publicKey,
})
} catch (error) { } catch (error) {
console.error('Wallet connection error:', error) console.error('Wallet connection error:', error)
setWalletState({ setWalletState({
connected: false, connected: false,
publicKey: null, publicKey: null,
type: null
}) })
} }
} }
const handleChatGeneration = (modelId: string, cost: number) => { const handleChatGeneration = (modelId: string, cost: number) => {
return async (prompt: string): Promise<ChatGenerationResult> => { return async (prompt: string): Promise<ChatGenerationResult> => {
if (!walletState.connected || !walletState.publicKey || !window.solflare) { if (!walletState.connected || !walletState.publicKey) {
return { error: 'Wallet not connected' } return { error: 'Wallet not connected' }
} }
@ -59,7 +39,7 @@ const Page: React.FC = (): React.ReactElement => {
const paymentResult = await processMTMPayment( const paymentResult = await processMTMPayment(
walletState.publicKey, walletState.publicKey,
cost, cost,
window.solflare walletState.type
) )
if (!paymentResult.success) { if (!paymentResult.success) {
@ -84,9 +64,8 @@ const Page: React.FC = (): React.ReactElement => {
</p> </p>
<WalletHeader <WalletHeader
isConnected={walletState.connected} walletState={walletState}
publicKey={walletState.publicKey} onConnect={handleConnect}
onConnect={connectWallet}
/> />
</div> </div>

View File

@ -1,30 +1,36 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import { WalletState, SUPPORTED_WALLETS } from '../services/walletService'
import { WalletType } from '../services/types'
interface WalletHeaderProps { interface WalletHeaderProps {
isConnected: boolean walletState: WalletState
publicKey: string | null onConnect: (walletType: WalletType) => Promise<void>
onConnect: () => Promise<void>
} }
const WalletHeader: React.FC<WalletHeaderProps> = ({ isConnected, publicKey, onConnect }) => { const WalletHeader: React.FC<WalletHeaderProps> = ({ walletState, onConnect }) => {
return ( return (
<div className="w-full bg-slate-900/50 backdrop-blur-lg rounded-xl shadow-lg border border-orange-800/50 mb-8 p-4"> <div className="w-full bg-slate-900/50 backdrop-blur-lg rounded-xl shadow-lg border border-orange-800/50 mb-8 p-4">
{!isConnected ? ( {!walletState.connected ? (
<div className="grid grid-cols-2 gap-4">
{SUPPORTED_WALLETS.map((wallet) => (
<button <button
onClick={onConnect} key={wallet.type}
className="w-full bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 onClick={() => onConnect(wallet.type)}
className="bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600
text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200
shadow-lg hover:shadow-orange-500/25" shadow-lg hover:shadow-orange-500/25"
> >
Connect Solflare Wallet Connect {wallet.name}
</button> </button>
))}
</div>
) : ( ) : (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-slate-300">Connected Wallet</span> <span className="text-slate-300">Connected Wallet</span>
<span className="px-3 py-1 bg-amber-500/20 rounded-full text-amber-200 text-sm"> <span className="px-3 py-1 bg-amber-500/20 rounded-full text-amber-200 text-sm">
{publicKey?.slice(0, 22)}... {walletState.publicKey?.slice(0, 22)}...
</span> </span>
</div> </div>
)} )}

View File

@ -6,22 +6,19 @@ import {
createAssociatedTokenAccountInstruction, createAssociatedTokenAccountInstruction,
ASSOCIATED_TOKEN_PROGRAM_ID ASSOCIATED_TOKEN_PROGRAM_ID
} from '@solana/spl-token' } from '@solana/spl-token'
import { WalletType } from './types'
// Constants
const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump' const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump'
const PAYMENT_RECEIVER_ADDRESS: string = 'FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY' const PAYMENT_RECEIVER_ADDRESS: string = 'FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY'
// RPC Configuration
const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4' const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
const SOLANA_WEBSOCKET_URL: string = 'wss://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4' const SOLANA_WEBSOCKET_URL: string = 'wss://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
// Initialize connection with WebSocket support
const connection = new Connection( const connection = new Connection(
SOLANA_RPC_URL, SOLANA_RPC_URL,
{ {
commitment: 'confirmed', commitment: 'confirmed',
wsEndpoint: SOLANA_WEBSOCKET_URL, wsEndpoint: SOLANA_WEBSOCKET_URL,
confirmTransactionInitialTimeout: 60000, // 60 seconds confirmTransactionInitialTimeout: 60000,
} }
) )
@ -44,12 +41,28 @@ async function findAssociatedTokenAddress(
)[0] )[0]
} }
interface WalletAdapter {
signAndSendTransaction(transaction: Transaction): Promise<{ signature: string }>
}
export async function processMTMPayment( export async function processMTMPayment(
walletPublicKey: string, walletPublicKey: string,
tokenAmount: number, tokenAmount: number,
solflareWallet: any walletType: WalletType
): Promise<PaymentResult> { ): Promise<PaymentResult> {
try { try {
let wallet: WalletAdapter | null = null;
if (walletType === 'phantom') {
wallet = window.phantom?.solana || null;
} else if (walletType === 'solflare') {
wallet = window.solflare || null;
}
if (!wallet) {
throw new Error(`${walletType} wallet not found`)
}
const senderPublicKey = new PublicKey(walletPublicKey) const senderPublicKey = new PublicKey(walletPublicKey)
const mintPublicKey = new PublicKey(MTM_TOKEN_MINT) const mintPublicKey = new PublicKey(MTM_TOKEN_MINT)
const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS) const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS)
@ -60,7 +73,6 @@ export async function processMTMPayment(
receiver: receiverPublicKey.toBase58(), receiver: receiverPublicKey.toBase58(),
}) })
// Find ATAs
const senderATA = await findAssociatedTokenAddress( const senderATA = await findAssociatedTokenAddress(
senderPublicKey, senderPublicKey,
mintPublicKey mintPublicKey
@ -78,21 +90,19 @@ export async function processMTMPayment(
const transaction = new Transaction() const transaction = new Transaction()
// Check if accounts exist
const [senderATAInfo, receiverATAInfo] = await Promise.all([ const [senderATAInfo, receiverATAInfo] = await Promise.all([
connection.getAccountInfo(senderATA), connection.getAccountInfo(senderATA),
connection.getAccountInfo(receiverATA), connection.getAccountInfo(receiverATA),
]) ])
// Create ATAs if they don't exist
if (!receiverATAInfo) { if (!receiverATAInfo) {
console.log('Creating receiver token account') console.log('Creating receiver token account')
transaction.add( transaction.add(
createAssociatedTokenAccountInstruction( createAssociatedTokenAccountInstruction(
senderPublicKey, // payer senderPublicKey,
receiverATA, // ata receiverATA,
receiverPublicKey, // owner receiverPublicKey,
mintPublicKey // mint mintPublicKey
) )
) )
} }
@ -101,21 +111,20 @@ export async function processMTMPayment(
console.log('Creating sender token account') console.log('Creating sender token account')
transaction.add( transaction.add(
createAssociatedTokenAccountInstruction( createAssociatedTokenAccountInstruction(
senderPublicKey, // payer senderPublicKey,
senderATA, // ata senderATA,
senderPublicKey, // owner senderPublicKey,
mintPublicKey // mint mintPublicKey
) )
) )
} }
// Add transfer instruction
transaction.add( transaction.add(
createTransferInstruction( createTransferInstruction(
senderATA, // from senderATA,
receiverATA, // to receiverATA,
senderPublicKey, // owner senderPublicKey,
BigInt(tokenAmount * (10 ** 6)) // amount BigInt(tokenAmount * (10 ** 6))
) )
) )
@ -124,7 +133,7 @@ export async function processMTMPayment(
transaction.feePayer = senderPublicKey transaction.feePayer = senderPublicKey
console.log('Sending transaction...') console.log('Sending transaction...')
const { signature } = await solflareWallet.signAndSendTransaction(transaction) const { signature } = await wallet.signAndSendTransaction(transaction)
console.log('Transaction sent:', signature) console.log('Transaction sent:', signature)
const confirmation = await connection.confirmTransaction({ const confirmation = await connection.confirmTransaction({
@ -147,3 +156,4 @@ export async function processMTMPayment(
} }
} }
} }

20
src/services/types.ts Normal file
View File

@ -0,0 +1,20 @@
export type WalletType = 'solflare' | 'phantom'
declare global {
interface Window {
solflare?: {
connect(): Promise<void>
disconnect(): Promise<void>
publicKey?: { toString(): string }
signAndSendTransaction(transaction: any): Promise<{ signature: string }>
}
phantom?: {
solana?: {
connect(): Promise<{ publicKey: { toString(): string } }>
disconnect(): Promise<void>
signAndSendTransaction(transaction: any): Promise<{ signature: string }>
}
}
}
}

View File

@ -0,0 +1,65 @@
import { WalletType } from './types'
export interface WalletState {
connected: boolean
publicKey: string | null
type: WalletType | null
}
export interface WalletConfig {
type: WalletType
name: string
connect: () => Promise<{ publicKey: string } | null>
}
const connectSolflare = async (): Promise<{ publicKey: string } | null> => {
if (!window.solflare) return null
await window.solflare.connect()
return window.solflare.publicKey ? { publicKey: window.solflare.publicKey.toString() } : null
}
const connectPhantom = async (): Promise<{ publicKey: string } | null> => {
if (!window.phantom?.solana) return null
try {
const response = await window.phantom.solana.connect()
return response.publicKey ? { publicKey: response.publicKey.toString() } : null
} catch {
return null
}
}
export const SUPPORTED_WALLETS: WalletConfig[] = [
{
type: 'solflare',
name: 'Solflare',
connect: connectSolflare
},
{
type: 'phantom',
name: 'Phantom',
connect: connectPhantom
}
]
export async function connectWallet(type: WalletType): Promise<WalletState> {
const wallet = SUPPORTED_WALLETS.find(w => w.type === type)
if (!wallet) throw new Error('Unsupported wallet')
try {
const result = await wallet.connect()
if (!result) throw new Error(`${wallet.name} not found`)
return {
connected: true,
publicKey: result.publicKey,
type: wallet.type
}
} catch (error) {
return {
connected: false,
publicKey: null,
type: null
}
}
}