@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.
Files changed (40) hide show
  1. package/README.md +126 -0
  2. package/dist/api/index.d.ts +10 -0
  3. package/dist/api/index.js +98 -0
  4. package/dist/auth/actions.d.ts +4 -0
  5. package/dist/auth/actions.js +37 -0
  6. package/dist/auth/index.d.ts +12 -0
  7. package/dist/auth/index.js +28 -0
  8. package/dist/auth/login-screen.d.ts +2 -0
  9. package/dist/auth/login-screen.jsx +49 -0
  10. package/dist/blockchain/index.d.ts +11 -0
  11. package/dist/blockchain/index.js +59 -0
  12. package/dist/checkout/index.d.ts +10 -0
  13. package/dist/checkout/index.jsx +235 -0
  14. package/dist/client.d.ts +3 -0
  15. package/dist/client.js +4 -0
  16. package/dist/config/index.d.ts +39 -0
  17. package/dist/config/index.js +44 -0
  18. package/dist/core/paygrid.d.ts +44 -0
  19. package/dist/core/paygrid.js +238 -0
  20. package/dist/core/privacy-wrapper.d.ts +29 -0
  21. package/dist/core/privacy-wrapper.js +72 -0
  22. package/dist/dashboard/api-section.d.ts +7 -0
  23. package/dist/dashboard/api-section.jsx +146 -0
  24. package/dist/dashboard/constant.d.ts +13 -0
  25. package/dist/dashboard/constant.jsx +13 -0
  26. package/dist/dashboard/index.d.ts +3 -0
  27. package/dist/dashboard/index.jsx +319 -0
  28. package/dist/dashboard/payment-table.d.ts +8 -0
  29. package/dist/dashboard/payment-table.jsx +85 -0
  30. package/dist/db/index.d.ts +15 -0
  31. package/dist/db/index.js +174 -0
  32. package/dist/index.css +2 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.js +6 -0
  35. package/dist/server.d.ts +3 -0
  36. package/dist/server.js +4 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/dist/types/index.d.ts +65 -0
  39. package/dist/types/index.js +20 -0
  40. package/package.json +60 -0
@@ -0,0 +1,235 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import { SUPPORTED_TOKENS } from "../config";
4
+ import { PaymentMethod } from "../types";
5
+ import { initWASM, isWASMSupported, ShadowWireClient, } from "@radr/shadowwire";
6
+ export function CheckoutModal({ amount: propsAmount, method: propsMethod, tokenSymbol: propsTokenSymbol, sender: propsSender, onPaymentResponse, }) {
7
+ const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
8
+ const [step, setStep] = useState("config");
9
+ const [selectedToken, setSelectedToken] = useState(propsTokenSymbol);
10
+ const [selectedMethod, setSelectedMethod] = useState(propsMethod === "manual-transfer"
11
+ ? PaymentMethod.MANUAL_TRANSFER
12
+ : PaymentMethod.WALLET_SIGNING);
13
+ const [client] = useState(() => new ShadowWireClient());
14
+ const [amount, setAmount] = useState(propsAmount);
15
+ const [activeIntent, setActiveIntent] = useState(null);
16
+ const [isLoading, setIsLoading] = useState(false);
17
+ const [error, setError] = useState(null);
18
+ const apiKey = process.env.NEXT_PUBLIC_PAYGRID_API_SECRET;
19
+ const [wasmInitialized, setWasmInitialized] = useState(false);
20
+ const [balance, setBalance] = useState(null);
21
+ useEffect(() => {
22
+ async function init() {
23
+ if (!isWASMSupported()) {
24
+ setError("WebAssembly not supported");
25
+ return;
26
+ }
27
+ try {
28
+ await initWASM("/wasm/settler_wasm_bg.wasm");
29
+ setWasmInitialized(true);
30
+ await loadBalance();
31
+ }
32
+ catch (err) {
33
+ setError("Initialization failed: " + err.message);
34
+ }
35
+ }
36
+ init();
37
+ }, []);
38
+ const loadBalance = async () => {
39
+ try {
40
+ const data = await client.getBalance(propsSender, selectedToken);
41
+ setBalance(data.available / 1e9);
42
+ }
43
+ catch (err) {
44
+ console.error("Balance load failed:", err);
45
+ }
46
+ };
47
+ const handleStartPayment = async () => {
48
+ try {
49
+ setIsLoading(true);
50
+ setError(null);
51
+ const headers = {};
52
+ if (apiKey)
53
+ headers["x-api-key"] = apiKey;
54
+ const response = await fetch("/api/paygrid/payment-intents", {
55
+ method: "POST",
56
+ headers: {
57
+ ...headers,
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({
61
+ amount,
62
+ method: propsMethod,
63
+ tokenSymbol: selectedToken,
64
+ sender: propsSender,
65
+ }),
66
+ });
67
+ // setStep("paying");
68
+ if (!response.ok) {
69
+ throw new Error(`Payment intent creation failed: ${response.statusText}`);
70
+ }
71
+ const data = (await response.json());
72
+ if (onPaymentResponse) {
73
+ onPaymentResponse(data);
74
+ }
75
+ setActiveIntent(data);
76
+ setStep("paying");
77
+ }
78
+ catch (err) {
79
+ const errorMessage = err instanceof Error ? err.message : "An error occurred";
80
+ setError(errorMessage);
81
+ console.error("Payment error:", err);
82
+ }
83
+ finally {
84
+ setIsLoading(false);
85
+ }
86
+ };
87
+ return (<div>
88
+ <button onClick={() => setIsCheckoutOpen(true)} className="bg-white/5 hover:bg-white/10 cursor-pointer border border-white/10 text-white px-8 py-4 rounded-2xl font-bold text-lg transition-all">
89
+ Pay now
90
+ </button>
91
+
92
+ {isCheckoutOpen && (<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
93
+ <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={() => setIsCheckoutOpen(false)}></div>
94
+
95
+ <div className="relative w-full max-w-md bg-[#111] border border-white/10 rounded-3xl overflow-hidden shadow-2xl">
96
+ <div className="p-6">
97
+ <div className="flex justify-between items-center mb-6">
98
+ <div className="flex items-center gap-2">
99
+ <div className="w-6 h-6 bg-indigo-600 rounded flex items-center justify-center text-[10px] font-bold">
100
+ P
101
+ </div>
102
+ <span className="font-bold">PayGrid checkout</span>
103
+ </div>
104
+ <button onClick={() => setIsCheckoutOpen(false)} className="text-gray-500 cursor-pointer hover:text-white">
105
+ &times;
106
+ </button>
107
+ </div>
108
+
109
+ {step === "config" && (<div className="space-y-6">
110
+ <div>
111
+ <label className="text-xs text-gray-500 font-bold uppercase block mb-2">
112
+ Select Token
113
+ </label>
114
+ <div className="grid grid-cols-3 gap-2">
115
+ {SUPPORTED_TOKENS.map((token) => (<button key={token.symbol} disabled={token.disabled} onClick={() => setSelectedToken(token.symbol)} className={`p-3 rounded-xl cursor-pointer border transition-all flex flex-col items-center gap-1 ${selectedToken === token.symbol ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5 hover:border-white/20"}`}>
116
+ <div className="w-6 h-6 rounded-full" style={{ backgroundColor: token.color }}></div>
117
+ <span className="text-sm font-medium">
118
+ {token.symbol}
119
+ </span>
120
+ </button>))}
121
+ </div>
122
+ </div>
123
+
124
+ <div>
125
+ <label className="text-xs text-gray-500 font-bold uppercase block mb-2">
126
+ Payment Method
127
+ </label>
128
+ <div className="space-y-2">
129
+ <button onClick={() => setSelectedMethod(PaymentMethod.WALLET_SIGNING)} className={`w-full p-4 cursor-pointer rounded-xl border text-left flex items-center gap-3 transition-all ${selectedMethod === PaymentMethod.WALLET_SIGNING ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5"}`}>
130
+ <div className="p-2 bg-white/5 rounded-lg">⚡</div>
131
+ <div>
132
+ <p className="text-sm font-semibold">
133
+ Wallet Signing
134
+ </p>
135
+ <p className="text-xs text-gray-500">
136
+ Sign with Phantom, Solflare or Backpack
137
+ </p>
138
+ </div>
139
+ </button>
140
+ <button onClick={() => setSelectedMethod(PaymentMethod.MANUAL_TRANSFER)} disabled className={`w-full p-4 cursor-pointer rounded-xl border text-left flex items-center gap-3 transition-all ${selectedMethod === PaymentMethod.MANUAL_TRANSFER ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5"}`}>
141
+ <div className="p-2 bg-white/5 rounded-lg">📋</div>
142
+ <div>
143
+ <p className="text-sm font-semibold">
144
+ Manual Transfer
145
+ </p>
146
+ <p className="text-xs text-gray-500">
147
+ Send tokens to a unique address
148
+ </p>
149
+ </div>
150
+ </button>
151
+ </div>
152
+ </div>
153
+
154
+ <div className="pt-4 border-t border-white/10">
155
+ <div className="flex justify-between items-end mb-4">
156
+ <span className="text-gray-400 text-sm">Total Due</span>
157
+ <div className="text-right">
158
+ <span className="text-2xl font-bold">
159
+ {amount} {selectedToken}
160
+ </span>
161
+ <p className="text-xs text-gray-500">{`~${amount} ${selectedToken}`}</p>
162
+ </div>
163
+ </div>
164
+ {error && (<div className="bg-red-500/10 border border-red-500/20 p-3 rounded-lg mb-4">
165
+ <p className="text-xs text-red-400">{error}</p>
166
+ </div>)}
167
+ <button onClick={handleStartPayment} disabled={isLoading} className="w-full bg-white text-black py-4 rounded-2xl font-bold hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
168
+ {isLoading ? "Processing..." : "Confirm & Pay"}
169
+ </button>
170
+ </div>
171
+ </div>)}
172
+
173
+ {step === "paying" && (<div className="text-center py-8">
174
+ <div className="relative w-24 h-24 mx-auto mb-6">
175
+ <div className="absolute inset-0 border-4 border-indigo-500/20 rounded-full"></div>
176
+ <div className="absolute inset-0 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
177
+ <div className="absolute inset-0 flex items-center justify-center font-bold text-lg">
178
+ {selectedToken}
179
+ </div>
180
+ </div>
181
+ <h3 className="text-xl font-bold mb-2">
182
+ Awaiting Transaction
183
+ </h3>
184
+ <p className="text-gray-500 text-sm mb-6 max-w-[280px] mx-auto">
185
+ Please complete the transaction in your wallet. We are
186
+ monitoring the Solana network.
187
+ </p>
188
+ <div className="bg-white/5 border border-white/10 p-4 rounded-2xl font-mono text-xs text-left">
189
+ <div className="flex justify-between mb-1">
190
+ <span className="text-gray-500">Network</span>
191
+ <span>Solana Mainnet</span>
192
+ </div>
193
+ <div className="flex justify-between mb-1">
194
+ <span className="text-gray-500">Status</span>
195
+ <span className="text-amber-400">Monitoring...</span>
196
+ </div>
197
+ <div className="flex justify-between">
198
+ <span className="text-gray-500">Intent ID</span>
199
+ <span>{activeIntent?.id}</span>
200
+ </div>
201
+ </div>
202
+ </div>)}
203
+
204
+ {step === "success" && (<div className="text-center py-8">
205
+ <div className="w-20 h-20 bg-emerald-500/20 text-emerald-500 rounded-full mx-auto flex items-center justify-center text-4xl mb-6">
206
+
207
+ </div>
208
+ <h3 className="text-2xl font-bold mb-2">Payment Settled!</h3>
209
+ <p className="text-gray-400 text-sm mb-6">
210
+ Your transaction has been confirmed on the blockchain.
211
+ </p>
212
+
213
+ {/* <div className="space-y-3 text-left">
214
+ <div className="bg-white/5 p-4 rounded-2xl border border-white/5">
215
+ <p className="text-[10px] text-gray-500 uppercase font-bold mb-1">
216
+ Transaction Hash
217
+ </p>
218
+ <p className="text-xs font-mono text-indigo-400 truncate">
219
+ {activeIntent?.transactionSignature}
220
+ </p>
221
+ </div>
222
+ </div> */}
223
+ </div>)}
224
+ </div>
225
+
226
+ <div className="bg-white/5 p-4 text-center">
227
+ <p className="text-[10px] text-gray-500 font-medium">
228
+ Powering the decentralized privacy economy with PayGrid
229
+ </p>
230
+ </div>
231
+ </div>
232
+ </div>)}
233
+ </div>);
234
+ }
235
+ export default CheckoutModal;
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export * from "./dashboard";
3
+ export * from "./checkout";
package/dist/client.js ADDED
@@ -0,0 +1,4 @@
1
+ // Client-side only exports - safe for browser bundling
2
+ export * from "./types";
3
+ export * from "./dashboard";
4
+ export * from "./checkout";
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { PayGridConfig } from '../types';
3
+ export declare const configSchema: z.ZodObject<{
4
+ SOLANA_RPC_URL: z.ZodString;
5
+ MERCHANT_WALLET_ADDRESS: z.ZodString;
6
+ DB_PATH: z.ZodDefault<z.ZodString>;
7
+ NEXT_PUBLIC_PAYGRID_API_SECRET: z.ZodString;
8
+ ADMIN_EMAIL: z.ZodString;
9
+ ADMIN_PASSWORD: z.ZodString;
10
+ NETWORK: z.ZodDefault<z.ZodEnum<["mainnet-beta", "devnet", "testnet"]>>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ NEXT_PUBLIC_PAYGRID_API_SECRET: string;
13
+ SOLANA_RPC_URL: string;
14
+ MERCHANT_WALLET_ADDRESS: string;
15
+ DB_PATH: string;
16
+ ADMIN_EMAIL: string;
17
+ ADMIN_PASSWORD: string;
18
+ NETWORK: "mainnet-beta" | "devnet" | "testnet";
19
+ }, {
20
+ NEXT_PUBLIC_PAYGRID_API_SECRET: string;
21
+ SOLANA_RPC_URL: string;
22
+ MERCHANT_WALLET_ADDRESS: string;
23
+ ADMIN_EMAIL: string;
24
+ ADMIN_PASSWORD: string;
25
+ DB_PATH?: string | undefined;
26
+ NETWORK?: "mainnet-beta" | "devnet" | "testnet" | undefined;
27
+ }>;
28
+ export declare function validateConfig(): PayGridConfig;
29
+ export declare const CONSTANTS: {
30
+ PAYMENT_EXPIRY_MS: number;
31
+ CHECK_INTERVAL_MS: number;
32
+ CONFIRMATIONS_REQUIRED: number;
33
+ };
34
+ export declare const SUPPORTED_TOKENS: {
35
+ symbol: string;
36
+ mint: string;
37
+ color: string;
38
+ disabled: boolean;
39
+ }[];
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ export const configSchema = z.object({
3
+ SOLANA_RPC_URL: z.string().url(),
4
+ MERCHANT_WALLET_ADDRESS: z.string().min(44), // Base58 encoded private key
5
+ DB_PATH: z.string().default('./paygrid.db'),
6
+ NEXT_PUBLIC_PAYGRID_API_SECRET: z.string().min(32),
7
+ ADMIN_EMAIL: z.string().email(),
8
+ ADMIN_PASSWORD: z.string().min(5),
9
+ NETWORK: z.enum(['mainnet-beta', 'devnet', 'testnet']).default('mainnet-beta'),
10
+ });
11
+ export function validateConfig() {
12
+ const result = configSchema.safeParse({
13
+ SOLANA_RPC_URL: process.env.NEXT_PUBLIC_SOLANA_RPC_URL,
14
+ MERCHANT_WALLET_ADDRESS: process.env.NEXT_PUBLIC_MERCHANT_WALLET_ADDRESS,
15
+ DB_PATH: process.env.DB_PATH,
16
+ NEXT_PUBLIC_PAYGRID_API_SECRET: process.env.NEXT_PUBLIC_PAYGRID_API_SECRET,
17
+ NETWORK: process.env.NEXT_PUBLIC_NETWORK,
18
+ ADMIN_EMAIL: process.env.NEXT_PUBLIC_ADMIN_EMAIL,
19
+ ADMIN_PASSWORD: process.env.NEXT_PUBLIC_ADMIN_PASSWORD,
20
+ });
21
+ if (!result.success) {
22
+ console.error('❌ Invalid PayGrid Configuration:', result.error.format());
23
+ throw new Error(`Invalid PayGrid configuration: ${result.error.message}`);
24
+ }
25
+ return {
26
+ rpcUrl: result.data.SOLANA_RPC_URL,
27
+ marchantWalletADdress: result.data.MERCHANT_WALLET_ADDRESS,
28
+ dbPath: result.data.DB_PATH,
29
+ apiSecret: result.data.NEXT_PUBLIC_PAYGRID_API_SECRET,
30
+ network: result.data.NETWORK,
31
+ adminEmail: result.data.ADMIN_EMAIL,
32
+ adminPassword: result.data.ADMIN_PASSWORD
33
+ };
34
+ }
35
+ export const CONSTANTS = {
36
+ PAYMENT_EXPIRY_MS: 30 * 60 * 1000, // 30 minutes
37
+ CHECK_INTERVAL_MS: 10 * 1000, // 10 seconds
38
+ CONFIRMATIONS_REQUIRED: 1,
39
+ };
40
+ export const SUPPORTED_TOKENS = [
41
+ { symbol: 'SOL', mint: '11111111111111111111111111111111', color: '#14F195', disabled: true },
42
+ { symbol: 'USDC', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', color: '#2775CA', disabled: false },
43
+ { symbol: 'BONK', mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', color: '#FFA500', disabled: true }
44
+ ];
@@ -0,0 +1,44 @@
1
+ import { AnalyticsData, PayGridConfig, PaymentIntent, TokenSymbol } from "../types";
2
+ import { DepositResponse } from "@radr/shadowwire";
3
+ export declare class PayGrid {
4
+ private db;
5
+ private solana;
6
+ private privacy;
7
+ private auth;
8
+ private config;
9
+ private watcherInterval?;
10
+ constructor(config: PayGridConfig);
11
+ init(): Promise<void>;
12
+ startWatcher(): Promise<void>;
13
+ stopWatcher(): void;
14
+ private checkPendingPayments;
15
+ createPaymentIntent(params: {
16
+ amount: number;
17
+ tokenSymbol: TokenSymbol;
18
+ method: "wallet-signing" | "manual-transfer";
19
+ sender?: string;
20
+ metadata?: Record<string, any>;
21
+ }): Promise<PaymentIntent & {
22
+ depositResponse: DepositResponse;
23
+ }>;
24
+ getPayment(id: string): Promise<PaymentIntent | undefined>;
25
+ createApiKey(name: string): Promise<{
26
+ key: string;
27
+ apiKey: import("../types").ApiKey;
28
+ }>;
29
+ validateApiKey(rawKey: string): Promise<boolean>;
30
+ listApiKeys(): Promise<import("../types").ApiKey[]>;
31
+ deleteApiKey(id: string): Promise<void>;
32
+ getPayments(): Promise<PaymentIntent[]>;
33
+ getAnalytics(days?: number): Promise<AnalyticsData>;
34
+ withdrawFromPrivacy(params: {
35
+ tokenSymbol: TokenSymbol;
36
+ recipient: string;
37
+ }): Promise<import("@radr/shadowwire").WithdrawResponse | undefined>;
38
+ transferFromPrivacy(params: {
39
+ tokenSymbol: TokenSymbol;
40
+ sender: string;
41
+ amount: number;
42
+ }): Promise<void>;
43
+ }
44
+ export declare function initPayGrid(config?: Partial<PayGridConfig>): Promise<PayGrid>;
@@ -0,0 +1,238 @@
1
+ import crypto from "crypto";
2
+ import { AuthService } from "../auth";
3
+ import { SolanaService } from "../blockchain";
4
+ import { CONSTANTS, SUPPORTED_TOKENS, validateConfig } from "../config";
5
+ import { PayGridDB } from "../db";
6
+ import { PaymentMethod, PaymentStatus, TokenSymbol, } from "../types";
7
+ import { PrivacyWrapper } from "./privacy-wrapper";
8
+ export class PayGrid {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.db = new PayGridDB(this.config.dbPath);
12
+ this.solana = new SolanaService(this.config.rpcUrl);
13
+ // Initialize Privacy Wrapper
14
+ this.privacy = new PrivacyWrapper(this.config);
15
+ this.auth = new AuthService(this.config.apiSecret);
16
+ }
17
+ async init() {
18
+ await this.db.init();
19
+ }
20
+ async startWatcher() {
21
+ console.log("🚀 PayGrid Watcher started");
22
+ // Run immediately once
23
+ await this.checkPendingPayments();
24
+ this.watcherInterval = setInterval(async () => {
25
+ try {
26
+ await this.checkPendingPayments();
27
+ }
28
+ catch (error) {
29
+ console.error("PayGrid Watcher Error:", error);
30
+ }
31
+ }, CONSTANTS.CHECK_INTERVAL_MS);
32
+ }
33
+ stopWatcher() {
34
+ if (this.watcherInterval) {
35
+ clearInterval(this.watcherInterval);
36
+ }
37
+ }
38
+ async checkPendingPayments() {
39
+ const pending = await this.db.getPendingPayments();
40
+ const now = Date.now();
41
+ for (const payment of pending) {
42
+ if (payment.expiresAt < now) {
43
+ await this.db.updatePaymentStatus(payment.id, "expired");
44
+ continue;
45
+ }
46
+ if (payment.method === PaymentMethod.MANUAL_TRANSFER &&
47
+ payment.walletAddress) {
48
+ const signature = await this.solana.findTransferTo(payment.walletAddress, payment.amount);
49
+ if (signature) {
50
+ await this.db.updatePaymentStatus(payment.id, "settled", signature);
51
+ }
52
+ }
53
+ else if (payment.status === "pending_confirmation" &&
54
+ payment.transactionSignature) {
55
+ const confirmed = await this.solana.confirmTransaction(payment.transactionSignature);
56
+ if (confirmed) {
57
+ await this.db.updatePaymentStatus(payment.id, "settled");
58
+ }
59
+ }
60
+ }
61
+ }
62
+ async createPaymentIntent(params) {
63
+ try {
64
+ const id = crypto.randomUUID();
65
+ // Resolve token mint if symbol is provided
66
+ let mintAddress;
67
+ let symbol = params.tokenSymbol;
68
+ if (params.tokenSymbol === TokenSymbol.SOL) {
69
+ mintAddress = SUPPORTED_TOKENS.find((t) => t.symbol === TokenSymbol.SOL)?.mint;
70
+ }
71
+ else if (params.tokenSymbol === TokenSymbol.USDC) {
72
+ mintAddress = SUPPORTED_TOKENS.find((t) => t.symbol === TokenSymbol.USDC)?.mint;
73
+ }
74
+ else if (params.tokenSymbol === TokenSymbol.BONK) {
75
+ mintAddress = SUPPORTED_TOKENS.find((t) => t.symbol === TokenSymbol.BONK)?.mint;
76
+ }
77
+ let response;
78
+ if (params.method === "wallet-signing") {
79
+ try {
80
+ const result = await this.privacy.createDepositTransaction({
81
+ amount: params.amount,
82
+ tokenMint: mintAddress ?? "",
83
+ symbol: params.tokenSymbol,
84
+ walletAddress: params.sender ?? "",
85
+ });
86
+ response = result;
87
+ }
88
+ catch (e) {
89
+ console.error("Failed to create privacy transaction", e);
90
+ throw new Error("Failed to create privacy transaction: " + e.message);
91
+ }
92
+ }
93
+ const amount = symbol === TokenSymbol.SOL
94
+ ? (response?.amount ?? 0) / 1e9
95
+ : (response?.amount ?? 0) / 1e6;
96
+ const payment = {
97
+ id,
98
+ amount: params.method === "wallet-signing" ? amount : params.amount,
99
+ tokenMint: mintAddress?.toString() ?? "",
100
+ tokenSymbol: symbol,
101
+ method: params.method,
102
+ status: PaymentStatus.AWAITING_PAYMENT,
103
+ walletAddress: this.config.marchantWalletADdress,
104
+ destination: this.config.marchantWalletADdress,
105
+ sender: params.sender,
106
+ expiresAt: Date.now() + CONSTANTS.PAYMENT_EXPIRY_MS,
107
+ createdAt: Date.now(),
108
+ metadata: params.metadata,
109
+ };
110
+ await this.db.createPayment(payment);
111
+ return { ...payment, depositResponse: response };
112
+ }
113
+ catch (error) {
114
+ console.error("Error creating payment intent:", error);
115
+ throw error;
116
+ }
117
+ }
118
+ async getPayment(id) {
119
+ return await this.db.getPayment(id);
120
+ }
121
+ async createApiKey(name) {
122
+ const { key, apiKey } = this.auth.generateApiKey(name);
123
+ await this.db.createApiKey(apiKey);
124
+ return { key, apiKey };
125
+ }
126
+ async validateApiKey(rawKey) {
127
+ const keys = await this.db.listApiKeys();
128
+ for (const key of keys) {
129
+ if (this.auth.verifyApiKey(rawKey, key.hashedKey)) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ async listApiKeys() {
136
+ return await this.db.listApiKeys();
137
+ }
138
+ async deleteApiKey(id) {
139
+ return await this.db.deleteApiKey(id);
140
+ }
141
+ async getPayments() {
142
+ return await this.db.getAllPayments();
143
+ }
144
+ async getAnalytics(days = 30) {
145
+ const payments = await this.db.getAllPayments();
146
+ const now = Date.now();
147
+ const startTime = now - days * 24 * 60 * 60 * 1000;
148
+ const settled = payments.filter((p) => p.status === "settled");
149
+ // Current period metrics
150
+ const filteredSettled = settled.filter((p) => p.createdAt >= startTime);
151
+ const totalRevenue = filteredSettled.reduce((sum, p) => sum + p.amount, 0);
152
+ // Previous period metrics
153
+ const prevStartTime = startTime - days * 24 * 60 * 60 * 1000;
154
+ const prevPeriodSettled = settled.filter((p) => p.createdAt >= prevStartTime && p.createdAt < startTime);
155
+ const pastRevenue = prevPeriodSettled.reduce((sum, p) => sum + p.amount, 0);
156
+ let revenueGrowth = 0;
157
+ if (pastRevenue > 0) {
158
+ revenueGrowth = ((totalRevenue - pastRevenue) / pastRevenue) * 100;
159
+ }
160
+ else if (totalRevenue > 0) {
161
+ revenueGrowth = 100;
162
+ }
163
+ // Group by date for history
164
+ const historyMap = new Map();
165
+ // Initialize map with all dates in the range to ensure zero values are shown
166
+ for (let i = days - 1; i >= 0; i--) {
167
+ const d = new Date(now - i * 24 * 60 * 60 * 1000);
168
+ const dateStr = d.toLocaleDateString("en-US", {
169
+ month: "short",
170
+ day: "numeric",
171
+ });
172
+ historyMap.set(dateStr, 0);
173
+ }
174
+ filteredSettled.forEach((p) => {
175
+ const date = new Date(p.createdAt).toLocaleDateString("en-US", {
176
+ month: "short",
177
+ day: "numeric",
178
+ });
179
+ if (historyMap.has(date)) {
180
+ historyMap.set(date, (historyMap.get(date) || 0) + p.amount);
181
+ }
182
+ });
183
+ const history = Array.from(historyMap.entries()).map(([date, amount]) => ({
184
+ date,
185
+ amount,
186
+ }));
187
+ // Filter all payments for the period for transactionCount and settlementRate calculations
188
+ const filteredPayments = payments.filter((p) => p.createdAt >= startTime);
189
+ const filteredSettledCount = filteredPayments.filter((p) => p.status === "settled").length;
190
+ return {
191
+ totalRevenue,
192
+ revenueGrowth,
193
+ pastRevenue,
194
+ transactionCount: filteredPayments.length,
195
+ settlementRate: filteredPayments.length > 0
196
+ ? (filteredSettledCount / filteredPayments.length) * 100
197
+ : 0,
198
+ history,
199
+ };
200
+ }
201
+ // Withdraw funds from the privacy pool to a recipient
202
+ async withdrawFromPrivacy(params) {
203
+ try {
204
+ const res = await this.privacy.withdraw({
205
+ recipient: params.recipient,
206
+ symbol: params.tokenSymbol,
207
+ });
208
+ console.log(res, "withdrawal response");
209
+ return res;
210
+ }
211
+ catch (error) {
212
+ console.error("Error withdrawing from privacy:", error);
213
+ throw error;
214
+ }
215
+ }
216
+ async transferFromPrivacy(params) {
217
+ try {
218
+ const res = await this.privacy.transfer({
219
+ symbol: params.tokenSymbol,
220
+ amount: params.amount,
221
+ sender: params.sender
222
+ });
223
+ }
224
+ catch (error) {
225
+ console.error("Error withdrawing from privacy:", error);
226
+ throw error;
227
+ }
228
+ }
229
+ }
230
+ export async function initPayGrid(config) {
231
+ const fullConfig = config
232
+ ? { ...validateConfig(), ...config }
233
+ : validateConfig();
234
+ const paygrid = new PayGrid(fullConfig);
235
+ await paygrid.init();
236
+ await paygrid.startWatcher();
237
+ return paygrid;
238
+ }
@@ -0,0 +1,29 @@
1
+ import { PayGridConfig } from "../types";
2
+ import { DepositResponse, TokenSymbol, TransferResponse } from "@radr/shadowwire";
3
+ export declare class PrivacyWrapper {
4
+ private client;
5
+ private rpcUrl;
6
+ private config;
7
+ constructor(config: PayGridConfig);
8
+ getPrivateBalance(walletAddress: string, symbol: TokenSymbol): Promise<import("@radr/shadowwire").PoolBalance>;
9
+ withdraw({ symbol, recipient }: {
10
+ symbol: string;
11
+ recipient: string;
12
+ }): Promise<import("@radr/shadowwire").WithdrawResponse | undefined>;
13
+ transfer({ symbol, sender, amount, }: {
14
+ symbol: string;
15
+ sender: string;
16
+ amount: number;
17
+ }): Promise<TransferResponse>;
18
+ createTransferTransaction(params: {
19
+ amount: number;
20
+ walletAddress: string;
21
+ symbol: TokenSymbol;
22
+ }): Promise<TransferResponse>;
23
+ createDepositTransaction(params: {
24
+ amount: number;
25
+ walletAddress: string;
26
+ tokenMint: string;
27
+ symbol: string;
28
+ }): Promise<DepositResponse>;
29
+ }