diff --git a/package-lock.json b/package-lock.json index 54574b2..54864b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "0.1.0", "dependencies": { "@fal-ai/client": "^1.2.1", + "@google/generative-ai": "^0.21.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.4", "next": "13.5.4", + "openai": "^4.77.0", "react": "^18", "react-dom": "^18" }, @@ -144,6 +146,14 @@ "node": ">=18.0.0" } }, + "node_modules/@google/generative-ai": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", + "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -752,6 +762,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -900,6 +919,17 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1176,6 +1206,11 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", @@ -1596,6 +1631,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -1767,6 +1813,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2567,6 +2621,14 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -2744,6 +2806,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3888,6 +3980,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -4005,6 +4116,24 @@ } } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -4191,6 +4320,39 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "4.77.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.77.0.tgz", + "integrity": "sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", + "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5649,6 +5811,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", @@ -5727,6 +5894,14 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index e84c55f..900bd51 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ }, "dependencies": { "@fal-ai/client": "^1.2.1", + "@google/generative-ai": "^0.21.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.4", "next": "13.5.4", + "openai": "^4.77.0", "react": "^18", "react-dom": "^18" }, diff --git a/src/app/api/adobe/route.ts b/src/app/api/adobe/route.ts new file mode 100644 index 0000000..db45637 --- /dev/null +++ b/src/app/api/adobe/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from 'next/server' + +if (!process.env.ADOBE_API_KEY || !process.env.ADOBE_CLIENT_SECRET) { + throw new Error('Adobe API credentials not configured') +} + +const ADOBE_API_URL = 'https://firefly-api.adobe.io + +export async function POST(req: NextRequest): Promise { + try { + const { prompt } = await req.json() + + if (!prompt) { + return NextResponse.json( + { error: 'Prompt is required' }, + { status: 400 } + ) + } + + // Adobe Firefly API request + const response = await fetch(ADOBE_API_URL, { + method: 'POST', + headers: { + 'x-api-key': process.env.ADOBE_API_KEY, + 'Authorization': `Bearer ${process.env.ADOBE_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + prompt: prompt, + n: 1, + size: { width: 1024, height: 1024 }, + styles: ["default"], + seed: Math.floor(Math.random() * 1000000), + quality: "standard", + contentClass: "photo" + }), + }) + + if (!response.ok) { + const errorData = await response.json() + console.error('Adobe API error:', errorData) + throw new Error(`Adobe API error: ${response.status}`) + } + + const data = await response.json() + console.log('Adobe generation result:', data) + + // Extract the image URL from the response + const imageUrl = data.outputs?.[0]?.image + + if (!imageUrl) { + throw new Error('No image URL in response') + } + + return NextResponse.json({ imageUrl }) + } catch (error) { + console.error('Adobe generation error:', error) + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to generate image' }, + { status: 500 } + ) + } +} + +export const dynamic = 'force-dynamic' diff --git a/src/app/api/gemini/route.ts b/src/app/api/gemini/route.ts new file mode 100644 index 0000000..d2ae571 --- /dev/null +++ b/src/app/api/gemini/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from 'next/server' +import { GoogleGenerativeAI } from '@google/generative-ai' + +if (!process.env.GEMINI_API_KEY) { + throw new Error('GEMINI_API_KEY is not configured in environment variables') +} + +const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY) +const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" }) + +export async function POST(req: NextRequest): Promise { + try { + const { prompt } = await req.json() + + if (!prompt) { + return NextResponse.json( + { error: 'Prompt is required' }, + { status: 400 } + ) + } + + const result = await model.generateContent(prompt) + const response = await result.response + const text = response.text() + + console.log('Gemini generation result:', text) + return NextResponse.json({ response: text }) + } catch (error) { + console.error('Gemini generation error:', error) + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to generate text' }, + { status: 500 } + ) + } +} + +export const dynamic = 'force-dynamic' diff --git a/src/app/api/grok/route.ts b/src/app/api/grok/route.ts new file mode 100644 index 0000000..17c6bb9 --- /dev/null +++ b/src/app/api/grok/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server' +import OpenAI from "openai"; + +if (!process.env.XAI_API_KEY) { + throw new Error('XAI_API_KEY is not configured in environment variables') +} + +const GROK_API_URL = 'https://api.openai.com/v1' + +export async function POST(req: NextRequest): Promise { + try { + const { prompt } = await req.json() + + if (!prompt) { + return NextResponse.json( + { error: 'Prompt is required' }, + { status: 400 } + ) + } + +const openai = new OpenAI({ + apiKey: process.env.XAI_API_KEY, + baseURL: GROK_API_URL, +}); + +const image = await openai.images.generate({ model: "dall-e-3", prompt: prompt }); + + + // Extract the image URL from Grok's response + const imageUrl = image.data[0].url + + if (!imageUrl) { + console.error('No image in response:', data) + throw new Error('No image generated in Grok API result') + } + + return NextResponse.json({ imageUrl }) + } catch (error) { + console.error('Grok generation error:', error) + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to generate image' }, + { status: 500 } + ) + } +} + +export const dynamic = 'force-dynamic' diff --git a/src/app/page.tsx b/src/app/page.tsx index 5d2b7ab..45cadd2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,10 @@ import WalletHeader from '../components/WalletHeader' import AIServiceCard from '../components/AIServiceCard' import TextGenerationCard from '../components/TextGenerationCard' import { generateWithFlux, FluxGenerationResult } from '../services/fluxService' +import { generateWithAdobe, AdobeGenerationResult } from '../services/adobeService' +import { generateWithGrok, GrokGenerationResult } from '../services/grokService' import { generateWithOllama, OllamaGenerationResult } from '../services/ollamaService' +import { generateWithGemini, GeminiGenerationResult } from '../services/geminiService' import { processMTMPayment } from '../services/paymentService' interface WalletState { @@ -65,6 +68,42 @@ const Page: React.FC = (): React.ReactElement => { return generateWithFlux(prompt) } + const handleGrokGeneration = async (prompt: string): Promise => { + if (!walletState.connected || !walletState.publicKey || !window.solflare) { + return { error: 'Wallet not connected' } + } + + const paymentResult = await processMTMPayment( + walletState.publicKey, + 5, // 5 MTM tokens for Grok premium service + window.solflare + ) + + if (!paymentResult.success) { + return { error: paymentResult.error } + } + + return generateWithGrok(prompt) + } + +const handleAdobeGeneration = async (prompt: string): Promise => { + if (!walletState.connected || !walletState.publicKey || !window.solflare) { + return { error: 'Wallet not connected' } + } + + const paymentResult = await processMTMPayment( + walletState.publicKey, + 4, // 4 MTM tokens for Adobe premium service + window.solflare + ) + + if (!paymentResult.success) { + return { error: paymentResult.error } + } + + return generateWithAdobe(prompt) + } + const handleOllamaGeneration = async (prompt: string): Promise => { if (!walletState.connected || !walletState.publicKey || !window.solflare) { return { error: 'Wallet not connected' } @@ -85,16 +124,36 @@ const Page: React.FC = (): React.ReactElement => { return generateWithOllama(prompt) } +const handleGeminiGeneration = async (prompt: string): Promise => { + if (!walletState.connected || !walletState.publicKey || !window.solflare) { + return { error: 'Wallet not connected' } + } + + // First process payment + const paymentResult = await processMTMPayment( + walletState.publicKey, + 3, // 3 MTM tokens for Gemini + window.solflare + ) + + if (!paymentResult.success) { + return { error: paymentResult.error } + } + + // Then generate text + return generateWithGemini(prompt) + } + return (
-
+
{/* Header */}

- AI Content Generator + Mark's Meme Market

- Generate amazing content using different AI models + Generate memes using various AI models

{
{/* AI Services Grid */} -
+
+ + + {/* + */} + + + {/* for another app { isWalletConnected={walletState.connected} onGenerate={handleOllamaGeneration} /> + + */}
{/* Info Section */}

- Powered by Flux AI and Ollama LLaMA2 • Requires MTM tokens for generation + Powered by Mark • Requires MTM tokens

diff --git a/src/services/adobeService.ts b/src/services/adobeService.ts new file mode 100644 index 0000000..ea27ec2 --- /dev/null +++ b/src/services/adobeService.ts @@ -0,0 +1,35 @@ +export interface AdobeGenerationResult { + imageUrl?: string + error?: string +} + +export async function generateWithAdobe(prompt: string): Promise { + try { + const response = await fetch('/api/adobe', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ prompt }), + }) + + if (!response.ok) { + throw new Error('Failed to generate image') + } + + const data = await response.json() + console.log('Raw Adobe Firefly response:', data) + + if (data.imageUrl) { + return { imageUrl: data.imageUrl } + } else { + console.error('Unexpected response structure:', data) + throw new Error('Invalid response format from Adobe API') + } + } catch (error) { + console.error('Adobe generation error:', error) + return { + error: error instanceof Error ? error.message : 'Generation failed' + } + } +} diff --git a/src/services/geminiService.ts b/src/services/geminiService.ts new file mode 100644 index 0000000..8e591d3 --- /dev/null +++ b/src/services/geminiService.ts @@ -0,0 +1,35 @@ +export interface GeminiGenerationResult { + textResponse?: string + error?: string +} + +export async function generateWithGemini(prompt: string): Promise { + try { + const response = await fetch('/api/gemini', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ prompt }), + }) + + if (!response.ok) { + throw new Error('Failed to generate text') + } + + const data = await response.json() + console.log('Raw Gemini response:', data) + + if (data.response) { + return { textResponse: data.response } + } else { + console.error('Unexpected response structure:', data) + throw new Error('Invalid response format from Gemini API') + } + } catch (error) { + console.error('Generation error:', error) + return { + error: error instanceof Error ? error.message : 'Generation failed' + } + } +} diff --git a/src/services/grokService.ts b/src/services/grokService.ts new file mode 100644 index 0000000..54ae2bf --- /dev/null +++ b/src/services/grokService.ts @@ -0,0 +1,35 @@ +export interface GrokGenerationResult { + imageUrl?: string + error?: string +} + +export async function generateWithGrok(prompt: string): Promise { + try { + const response = await fetch('/api/grok', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ prompt }), + }) + + if (!response.ok) { + throw new Error('Failed to generate image') + } + + const data = await response.json() + console.log('Raw Grok response:', data) + + if (data.imageUrl) { + return { imageUrl: data.imageUrl } + } else { + console.error('Unexpected response structure:', data) + throw new Error('Invalid response format from Grok API') + } + } catch (error) { + console.error('Grok generation error:', error) + return { + error: error instanceof Error ? error.message : 'Generation failed' + } + } +} diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 2e5f04c..952f509 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -1,4 +1,4 @@ -import { Connection, PublicKey, Transaction } from '@solana/web3.js' +import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js' import { TOKEN_PROGRAM_ID, createTransferInstruction, @@ -9,7 +9,7 @@ import { // Constants const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump' -const PAYMENT_RECEIVER_ADDRESS: string = '9B3mGyeJTUN7ZTqyLWHLL37zL92eif239hH2pYSkvq8J' +const PAYMENT_RECEIVER_ADDRESS: string = 'FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY' // RPC Configuration const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4' @@ -30,6 +30,20 @@ export interface PaymentResult { error?: string } +async function findAssociatedTokenAddress( + walletAddress: PublicKey, + tokenMintAddress: PublicKey +): Promise { + return PublicKey.findProgramAddressSync( + [ + walletAddress.toBuffer(), + TOKEN_PROGRAM_ID.toBuffer(), + tokenMintAddress.toBuffer(), + ], + ASSOCIATED_TOKEN_PROGRAM_ID + )[0] +} + export async function processMTMPayment( walletPublicKey: string, tokenAmount: number, @@ -40,69 +54,88 @@ export async function processMTMPayment( const mintPublicKey = new PublicKey(MTM_TOKEN_MINT) const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS) - const senderATA = await getAssociatedTokenAddress( - mintPublicKey, - senderPublicKey + console.log('Processing payment with keys:', { + sender: senderPublicKey.toBase58(), + mint: mintPublicKey.toBase58(), + receiver: receiverPublicKey.toBase58(), + }) + + // Find ATAs + const senderATA = await findAssociatedTokenAddress( + senderPublicKey, + mintPublicKey ) - const receiverATA = await getAssociatedTokenAddress( - mintPublicKey, - receiverPublicKey + + const receiverATA = await findAssociatedTokenAddress( + receiverPublicKey, + mintPublicKey ) + console.log('Token accounts:', { + senderATA: senderATA.toBase58(), + receiverATA: receiverATA.toBase58(), + }) + const transaction = new Transaction() - // Check and create ATAs if needed - const receiverATAInfo = await connection.getAccountInfo(receiverATA) + // Check if accounts exist + const [senderATAInfo, receiverATAInfo] = await Promise.all([ + connection.getAccountInfo(senderATA), + connection.getAccountInfo(receiverATA), + ]) + + // Create ATAs if they don't exist if (!receiverATAInfo) { + console.log('Creating receiver token account') transaction.add( - createATAInstruction( - senderPublicKey, - receiverATA, - receiverPublicKey, - mintPublicKey, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID + createAssociatedTokenAccountInstruction( + senderPublicKey, // payer + receiverATA, // ata + receiverPublicKey, // owner + mintPublicKey // mint ) ) } - const senderATAInfo = await connection.getAccountInfo(senderATA) if (!senderATAInfo) { + console.log('Creating sender token account') transaction.add( - createATAInstruction( - senderPublicKey, - senderATA, - senderPublicKey, - mintPublicKey, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID + createAssociatedTokenAccountInstruction( + senderPublicKey, // payer + senderATA, // ata + senderPublicKey, // owner + mintPublicKey // mint ) ) } // Add transfer instruction - const transferInstruction = createTransferInstruction( - senderATA, - receiverATA, - senderPublicKey, - BigInt(tokenAmount * (10 ** 6)) + transaction.add( + createTransferInstruction( + senderATA, // from + receiverATA, // to + senderPublicKey, // owner + BigInt(tokenAmount * (10 ** 6)) // amount + ) ) - transaction.add(transferInstruction) const latestBlockhash = await connection.getLatestBlockhash('confirmed') transaction.recentBlockhash = latestBlockhash.blockhash transaction.feePayer = senderPublicKey + console.log('Sending transaction...') const { signature } = await solflareWallet.signAndSendTransaction(transaction) - + console.log('Transaction sent:', signature) + const confirmation = await connection.confirmTransaction({ signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }) + }, 'confirmed') if (confirmation.value.err) { - return { success: false, error: 'Payment failed to confirm' } + console.error('Transaction error:', confirmation.value.err) + throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) } return { success: true }