@qhristen/paygrid 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +98 -0
- package/dist/auth/actions.d.ts +4 -0
- package/dist/auth/actions.js +37 -0
- package/dist/auth/index.d.ts +12 -0
- package/dist/auth/index.js +28 -0
- package/dist/auth/login-screen.d.ts +2 -0
- package/dist/auth/login-screen.jsx +49 -0
- package/dist/blockchain/index.d.ts +11 -0
- package/dist/blockchain/index.js +59 -0
- package/dist/checkout/index.d.ts +10 -0
- package/dist/checkout/index.jsx +235 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +4 -0
- package/dist/config/index.d.ts +39 -0
- package/dist/config/index.js +44 -0
- package/dist/core/paygrid.d.ts +44 -0
- package/dist/core/paygrid.js +238 -0
- package/dist/core/privacy-wrapper.d.ts +29 -0
- package/dist/core/privacy-wrapper.js +72 -0
- package/dist/dashboard/api-section.d.ts +7 -0
- package/dist/dashboard/api-section.jsx +146 -0
- package/dist/dashboard/constant.d.ts +13 -0
- package/dist/dashboard/constant.jsx +13 -0
- package/dist/dashboard/index.d.ts +3 -0
- package/dist/dashboard/index.jsx +319 -0
- package/dist/dashboard/payment-table.d.ts +8 -0
- package/dist/dashboard/payment-table.jsx +85 -0
- package/dist/db/index.d.ts +15 -0
- package/dist/db/index.js +174 -0
- package/dist/index.css +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/index.js +20 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# PayGrid
|
|
2
|
+
|
|
3
|
+
**PayGrid** is a self-hosted, embeddable Web3 payments infrastructure for Solana. It allows you to accept SOL and SPL token payments directly within your Next.js application without relying on third-party payment processors.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
- **Self-Hosted:** Full control over your data and treasury keys.
|
|
10
|
+
- **Dual Payment Flows:**
|
|
11
|
+
- **Wallet-Signing:** Direct transaction signing with Solana wallets (Phantom, Solflare, etc.).
|
|
12
|
+
- **Manual Transfer:** Unique temporary wallet generation for manual transfers.
|
|
13
|
+
- **Embedded Dashboard:** Built-in React components for managing payments and API keys.
|
|
14
|
+
- **SQLite Powered:** No heavy database setup required; data is stored locally in an embedded SQLite file.
|
|
15
|
+
- **Status Monitoring:** Background watcher automatically settles payments by monitoring the blockchain.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🛠 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @paygrid/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🏗 Setup
|
|
28
|
+
|
|
29
|
+
### 1. Environment Variables
|
|
30
|
+
|
|
31
|
+
Create a `.env` file in your Next.js project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
|
35
|
+
TREASURY_PRIVATE_KEY=YourBase58PrivateKey...
|
|
36
|
+
NEXT_PUBLIC_PAYGRID_API_SECRET=at_least_32_character_random_string_for_paygrid_test
|
|
37
|
+
=at_least_32_character_random_string
|
|
38
|
+
DB_PATH=./paygrid.db # Default
|
|
39
|
+
NETWORK=mainnet-beta # Or devnet
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Initialize PayGrid (API Route)
|
|
43
|
+
|
|
44
|
+
Create a file at `app/api/paygrid/[...path]/route.ts`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { initPayGrid } from '@paygrid/core';
|
|
48
|
+
import { createApiHandler } from '@paygrid/core/api';
|
|
49
|
+
|
|
50
|
+
// Initialize core
|
|
51
|
+
const paygrid = await initPayGrid();
|
|
52
|
+
|
|
53
|
+
// Create handler
|
|
54
|
+
const handler = createApiHandler(paygrid);
|
|
55
|
+
|
|
56
|
+
export { handler as GET, handler as POST };
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Embed the Dashboard
|
|
60
|
+
|
|
61
|
+
Create a dashboard page at `app/dashboard/payments/page.tsx`:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
'use client';
|
|
65
|
+
|
|
66
|
+
import { PayGridDashboard } from '@paygrid/core';
|
|
67
|
+
import '@paygrid/core/dist/index.css'; // Import the isolated styles
|
|
68
|
+
|
|
69
|
+
export default function DashboardPage() {
|
|
70
|
+
return (
|
|
71
|
+
<div className="pg-dashboard-wrapper">
|
|
72
|
+
<PayGridDashboard apiUrl="/api/paygrid" />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 💳 Usage
|
|
81
|
+
|
|
82
|
+
### Create a Payment Intent
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const response = await fetch('/api/paygrid/payment-intents', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'x-api-key': 'your_api_key',
|
|
89
|
+
'Content-Type': 'application/json'
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
amount: 1.5,
|
|
93
|
+
tokenMint: 'SOL',
|
|
94
|
+
method: 'wallet-signing', // or 'manual-transfer'
|
|
95
|
+
metadata: { orderId: '12345' }
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const intent = await response.json();
|
|
100
|
+
console.log('Payment Intent Created:', intent.id);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🔐 Security
|
|
106
|
+
|
|
107
|
+
- **Treasury Keys:** Your treasury private key is only used to sign transactions for settlements (if applicable) and is never exposed via the API.
|
|
108
|
+
- **API Keys:** Keys are hashed using `bcrypt` before being stored in the database.
|
|
109
|
+
- **Validation:** Every transaction is validated against the blockchain state before being marked as `settled`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🧱 Architecture
|
|
114
|
+
|
|
115
|
+
- `core/`: Payment lifecycle and state machine logic.
|
|
116
|
+
- `blockchain/`: Solana Web3.js integration and watchers.
|
|
117
|
+
- `db/`: Embedded SQLite storage.
|
|
118
|
+
- `api/`: Next.js request handlers.
|
|
119
|
+
- `dashboard/`: React UI components.
|
|
120
|
+
- `auth/`: API key generation and validation.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## ⚖️ License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { PayGrid } from "../core/paygrid";
|
|
3
|
+
export declare function createApiHandler(paygrid: PayGrid): (req: NextRequest) => Promise<NextResponse<import("../types").PaymentIntent> | NextResponse<import("../types").PaymentIntent[]> | NextResponse<import("../types").AnalyticsData> | NextResponse<{
|
|
4
|
+
key: string;
|
|
5
|
+
apiKey: import("../types").ApiKey;
|
|
6
|
+
}> | NextResponse<import("../types").ApiKey[]> | NextResponse<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
}> | NextResponse<import("@radr/shadowwire").WithdrawResponse | undefined> | NextResponse<void> | NextResponse<{
|
|
9
|
+
error: any;
|
|
10
|
+
}>>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
export function createApiHandler(paygrid) {
|
|
3
|
+
return async function handler(req) {
|
|
4
|
+
const { pathname } = new URL(req.url);
|
|
5
|
+
const pathParts = pathname.split("/").filter(Boolean);
|
|
6
|
+
const resource = pathParts[2]; // /api/paygrid/[resource]
|
|
7
|
+
const id = pathParts[3];
|
|
8
|
+
// Auth check
|
|
9
|
+
const apiKey = req.headers.get("x-api-key");
|
|
10
|
+
const isValid = apiKey ? await paygrid.validateApiKey(apiKey) : false;
|
|
11
|
+
if (!isValid) {
|
|
12
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
if (resource === "payment-intents") {
|
|
16
|
+
if (req.method === "POST") {
|
|
17
|
+
const body = await req.json();
|
|
18
|
+
const intent = await paygrid.createPaymentIntent(body);
|
|
19
|
+
return NextResponse.json(intent);
|
|
20
|
+
}
|
|
21
|
+
if (req.method === "GET" && id) {
|
|
22
|
+
const intent = await paygrid.getPayment(id);
|
|
23
|
+
if (!intent)
|
|
24
|
+
return NextResponse.json({ error: "Not Found" }, { status: 404 });
|
|
25
|
+
return NextResponse.json(intent);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (resource === "payments") {
|
|
29
|
+
if (req.method === "GET") {
|
|
30
|
+
const payments = await paygrid.getPayments();
|
|
31
|
+
return NextResponse.json(payments);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (resource === "analytics") {
|
|
35
|
+
if (req.method === "GET") {
|
|
36
|
+
const { searchParams } = new URL(req.url);
|
|
37
|
+
const days = parseInt(searchParams.get("days") || "30", 10);
|
|
38
|
+
const stats = await paygrid.getAnalytics(days);
|
|
39
|
+
return NextResponse.json(stats);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (resource === "api-keys") {
|
|
43
|
+
if (req.method === "POST") {
|
|
44
|
+
const { name } = await req.json();
|
|
45
|
+
const result = await paygrid.createApiKey(name);
|
|
46
|
+
return NextResponse.json(result);
|
|
47
|
+
}
|
|
48
|
+
if (req.method === "GET") {
|
|
49
|
+
const keys = await paygrid.listApiKeys();
|
|
50
|
+
return NextResponse.json(keys);
|
|
51
|
+
}
|
|
52
|
+
if (req.method === "DELETE" && id) {
|
|
53
|
+
await paygrid.deleteApiKey(id);
|
|
54
|
+
return NextResponse.json({ success: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (resource === "privacy-withdraw") {
|
|
58
|
+
if (req.method === "POST") {
|
|
59
|
+
const body = await req.json();
|
|
60
|
+
const { tokenSymbol, recipient } = body;
|
|
61
|
+
if (!tokenSymbol || !recipient) {
|
|
62
|
+
return NextResponse.json({ error: "Missing parameters" }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
const result = await paygrid.withdrawFromPrivacy({
|
|
65
|
+
tokenSymbol: tokenSymbol,
|
|
66
|
+
recipient,
|
|
67
|
+
});
|
|
68
|
+
return NextResponse.json(result);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (resource === "privacy-transfer") {
|
|
72
|
+
if (req.method === "POST") {
|
|
73
|
+
const body = await req.json();
|
|
74
|
+
const { tokenSymbol, amount, sender } = body;
|
|
75
|
+
if (!tokenSymbol) {
|
|
76
|
+
return NextResponse.json({ error: "Missing parameters" }, { status: 400 });
|
|
77
|
+
}
|
|
78
|
+
const result = await paygrid.transferFromPrivacy({
|
|
79
|
+
tokenSymbol: tokenSymbol,
|
|
80
|
+
amount,
|
|
81
|
+
sender,
|
|
82
|
+
});
|
|
83
|
+
return NextResponse.json(result);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return NextResponse.json({ error: "Method Not Allowed" }, { status: 405 });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error("API Error Details:", {
|
|
90
|
+
message: error.message,
|
|
91
|
+
stack: error.stack,
|
|
92
|
+
resource,
|
|
93
|
+
method: req.method,
|
|
94
|
+
});
|
|
95
|
+
return NextResponse.json({ error: error.message || "Internal Server Error" }, { status: 500 });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import { redirect } from 'next/navigation';
|
|
4
|
+
export async function login(prevState, formData) {
|
|
5
|
+
const email = formData.get('email');
|
|
6
|
+
const password = formData.get('password');
|
|
7
|
+
const adminEmail = process.env.NEXT_PUBLIC_ADMIN_EMAIL;
|
|
8
|
+
const adminPassword = process.env.NEXT_PUBLIC_ADMIN_PASSWORD;
|
|
9
|
+
if (!adminEmail || !adminPassword) {
|
|
10
|
+
return { error: 'Server configuration error: Admin credentials not set in environment.' };
|
|
11
|
+
}
|
|
12
|
+
if (email === adminEmail && password === adminPassword) {
|
|
13
|
+
const cookieStore = await cookies();
|
|
14
|
+
cookieStore.set('paygrid_admin_session', 'true', {
|
|
15
|
+
httpOnly: false,
|
|
16
|
+
secure: process.env.NODE_ENV === 'production',
|
|
17
|
+
path: '/',
|
|
18
|
+
maxAge: 60 * 60 * 24 // 1 day
|
|
19
|
+
});
|
|
20
|
+
redirect('/dashboard');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return { error: 'Invalid email or password' };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function logout() {
|
|
27
|
+
const cookieStore = await cookies();
|
|
28
|
+
// Delete the admin session cookie with same path and immediate expiration
|
|
29
|
+
cookieStore.set('paygrid_admin_session', '', {
|
|
30
|
+
httpOnly: false,
|
|
31
|
+
secure: process.env.NODE_ENV === 'production',
|
|
32
|
+
path: '/',
|
|
33
|
+
maxAge: 0,
|
|
34
|
+
});
|
|
35
|
+
cookieStore.delete("paygrid_admin_session");
|
|
36
|
+
redirect('/');
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiKey } from '../types';
|
|
2
|
+
export declare class AuthService {
|
|
3
|
+
private apiSecret;
|
|
4
|
+
constructor(apiSecret: string);
|
|
5
|
+
generateApiKey(name: string): {
|
|
6
|
+
key: string;
|
|
7
|
+
apiKey: ApiKey;
|
|
8
|
+
};
|
|
9
|
+
verifyApiKey(rawKey: string, hashedKey: string): boolean;
|
|
10
|
+
}
|
|
11
|
+
export * from './login-screen';
|
|
12
|
+
export * from './actions';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import bcrypt from 'bcryptjs';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
export class AuthService {
|
|
4
|
+
constructor(apiSecret) {
|
|
5
|
+
this.apiSecret = apiSecret;
|
|
6
|
+
}
|
|
7
|
+
generateApiKey(name) {
|
|
8
|
+
const rawKey = `pg_${crypto.randomBytes(32).toString('hex')}`;
|
|
9
|
+
const id = crypto.randomUUID();
|
|
10
|
+
const keyHint = rawKey.substring(0, 7); // pg_ + 4 chars
|
|
11
|
+
const hashedKey = bcrypt.hashSync(rawKey, 10);
|
|
12
|
+
return {
|
|
13
|
+
key: rawKey,
|
|
14
|
+
apiKey: {
|
|
15
|
+
id,
|
|
16
|
+
keyHint,
|
|
17
|
+
hashedKey,
|
|
18
|
+
name,
|
|
19
|
+
createdAt: Date.now(),
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
verifyApiKey(rawKey, hashedKey) {
|
|
24
|
+
return bcrypt.compareSync(rawKey, hashedKey);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export * from './login-screen';
|
|
28
|
+
export * from './actions';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useActionState } from 'react';
|
|
3
|
+
import { login } from './actions';
|
|
4
|
+
import { Loader2 } from 'lucide-react';
|
|
5
|
+
export function LoginScreen() {
|
|
6
|
+
const [state, formAction, isPending] = useActionState(login, null);
|
|
7
|
+
return (<div className="min-h-screen flex items-center justify-center bg-[#050505] text-white overflow-hidden relative">
|
|
8
|
+
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-indigo-900/20 via-[#050505] to-[#050505] z-0 pointer-events-none"/>
|
|
9
|
+
|
|
10
|
+
<div className="w-full max-w-md p-8 rounded-2xl border border-white/10 bg-[#111]/80 backdrop-blur-xl shadow-2xl z-10">
|
|
11
|
+
<div className="mb-8 text-center">
|
|
12
|
+
<div className="mx-auto w-12 h-12 bg-indigo-500 rounded-xl flex items-center justify-center mb-4 shadow-lg shadow-indigo-500/20">
|
|
13
|
+
<span className="text-xl font-bold text-white">P</span>
|
|
14
|
+
</div>
|
|
15
|
+
<h1 className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400">Welcome Back</h1>
|
|
16
|
+
<p className="text-gray-500 text-sm mt-2">Sign in to your PayGrid dashboard</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<form action={formAction} className="space-y-4">
|
|
20
|
+
<div className="space-y-2">
|
|
21
|
+
<label htmlFor="email" className="text-sm font-medium text-gray-300">Email</label>
|
|
22
|
+
<input id="email" name="email" type="email" required placeholder="admin@paygrid.com" className="w-full bg-black/50 border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all placeholder:text-gray-600 hover:border-white/20"/>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="space-y-2">
|
|
26
|
+
<label htmlFor="password" className="text-sm font-medium text-gray-300">Password</label>
|
|
27
|
+
<input id="password" name="password" type="password" required placeholder="••••••••" className="w-full bg-black/50 border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all placeholder:text-gray-600 hover:border-white/20"/>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
{state?.error && (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm text-center">
|
|
31
|
+
{state.error}
|
|
32
|
+
</div>)}
|
|
33
|
+
|
|
34
|
+
<button type="submit" disabled={isPending} className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-3 rounded-lg transition-all shadow-lg shadow-indigo-600/20 flex items-center justify-center gap-2 mt-6 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
35
|
+
{isPending ? (<>
|
|
36
|
+
<Loader2 className="w-4 h-4 animate-spin"/>
|
|
37
|
+
Signing in...
|
|
38
|
+
</>) : ("Sign In")}
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
<div className="mt-6 text-center">
|
|
42
|
+
<p className="text-xs text-gray-600">
|
|
43
|
+
Secure admin access only
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
48
|
+
</div>);
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class SolanaService {
|
|
2
|
+
private connection;
|
|
3
|
+
constructor(rpcUrl: string);
|
|
4
|
+
generateTemporaryWallet(): Promise<{
|
|
5
|
+
publicKey: string;
|
|
6
|
+
privateKey: string;
|
|
7
|
+
}>;
|
|
8
|
+
checkBalance(publicKey: string): Promise<number>;
|
|
9
|
+
confirmTransaction(signature: string): Promise<boolean>;
|
|
10
|
+
findTransferTo(targetWallet: string, amount: number): Promise<string | null>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import bs58 from "bs58";
|
|
3
|
+
export class SolanaService {
|
|
4
|
+
constructor(rpcUrl) {
|
|
5
|
+
this.connection = new Connection(rpcUrl, "confirmed");
|
|
6
|
+
}
|
|
7
|
+
async generateTemporaryWallet() {
|
|
8
|
+
const keypair = Keypair.generate();
|
|
9
|
+
return {
|
|
10
|
+
publicKey: keypair.publicKey.toBase58(),
|
|
11
|
+
privateKey: bs58.encode(keypair.secretKey),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async checkBalance(publicKey) {
|
|
15
|
+
const balance = await this.connection.getBalance(new PublicKey(publicKey));
|
|
16
|
+
return balance / LAMPORTS_PER_SOL;
|
|
17
|
+
}
|
|
18
|
+
async confirmTransaction(signature) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await this.connection.confirmTransaction(signature, "confirmed");
|
|
21
|
+
return !result.value.err;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("Error confirming transaction:", error);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async findTransferTo(targetWallet, amount) {
|
|
29
|
+
const pubkey = new PublicKey(targetWallet);
|
|
30
|
+
const signatures = await this.connection.getSignaturesForAddress(pubkey, {
|
|
31
|
+
limit: 10,
|
|
32
|
+
});
|
|
33
|
+
for (const sigInfo of signatures) {
|
|
34
|
+
const tx = await this.connection.getTransaction(sigInfo.signature, {
|
|
35
|
+
commitment: "confirmed",
|
|
36
|
+
maxSupportedTransactionVersion: 0, // 👈 REQUIRED now
|
|
37
|
+
});
|
|
38
|
+
if (!tx)
|
|
39
|
+
continue;
|
|
40
|
+
// Basic SOL transfer check - this is simplified for now
|
|
41
|
+
// A more robust check would involve parsing instructions
|
|
42
|
+
const meta = tx.meta;
|
|
43
|
+
if (!meta)
|
|
44
|
+
continue;
|
|
45
|
+
const preBalances = meta.preBalances;
|
|
46
|
+
const postBalances = meta.postBalances;
|
|
47
|
+
const accountKeys = tx.transaction.message.staticAccountKeys;
|
|
48
|
+
const targetIndex = accountKeys.findIndex((key) => key.equals(pubkey));
|
|
49
|
+
if (targetIndex === -1)
|
|
50
|
+
continue;
|
|
51
|
+
const receivedAmount = (postBalances[targetIndex] - preBalances[targetIndex]) /
|
|
52
|
+
LAMPORTS_PER_SOL;
|
|
53
|
+
if (receivedAmount >= amount) {
|
|
54
|
+
return sigInfo.signature;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PayGridResponseType } from "../types";
|
|
2
|
+
export interface CheckoutModalProps {
|
|
3
|
+
amount: number;
|
|
4
|
+
method: "wallet-signing" | "manual-transfer";
|
|
5
|
+
tokenSymbol: string;
|
|
6
|
+
sender: string;
|
|
7
|
+
onPaymentResponse?: (response: PayGridResponseType) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function CheckoutModal({ amount: propsAmount, method: propsMethod, tokenSymbol: propsTokenSymbol, sender: propsSender, onPaymentResponse, }: CheckoutModalProps): import("react").JSX.Element;
|
|
10
|
+
export default CheckoutModal;
|