decharge-scout 1.0.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/src/wallet.js ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Wallet Operations Module
3
+ *
4
+ * Handles Solana wallet loading, staking, and refund operations
5
+ */
6
+
7
+ import {
8
+ Connection,
9
+ Keypair,
10
+ PublicKey,
11
+ Transaction,
12
+ SystemProgram,
13
+ LAMPORTS_PER_SOL,
14
+ sendAndConfirmTransaction
15
+ } from '@solana/web3.js';
16
+ import { readFileSync } from 'fs';
17
+ import dotenv from 'dotenv';
18
+
19
+ dotenv.config();
20
+
21
+ // Solana connection
22
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
23
+ const connection = new Connection(RPC_URL, 'confirmed');
24
+
25
+ /**
26
+ * Load wallet from JSON keypair file
27
+ */
28
+ export async function loadWallet(walletPath) {
29
+ try {
30
+ const keypairData = JSON.parse(readFileSync(walletPath, 'utf-8'));
31
+ const secretKey = Uint8Array.from(keypairData);
32
+ const keypair = Keypair.fromSecretKey(secretKey);
33
+
34
+ // Check balance
35
+ const balance = await connection.getBalance(keypair.publicKey);
36
+ const balanceSOL = balance / LAMPORTS_PER_SOL;
37
+
38
+ if (balanceSOL < 0.02) {
39
+ throw new Error(`Insufficient balance: ${balanceSOL} SOL. Need at least 0.02 SOL for stake + fees.`);
40
+ }
41
+
42
+ return keypair;
43
+ } catch (error) {
44
+ if (error.code === 'ENOENT') {
45
+ throw new Error(`Wallet file not found: ${walletPath}`);
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Get Solana connection
53
+ */
54
+ export function getConnection() {
55
+ return connection;
56
+ }
57
+
58
+ /**
59
+ * Stake SOL to escrow account
60
+ */
61
+ export async function stakeSOL(wallet, amount) {
62
+ try {
63
+ const escrowPubkey = getEscrowAddress();
64
+
65
+ // Create transfer transaction
66
+ const transaction = new Transaction().add(
67
+ SystemProgram.transfer({
68
+ fromPubkey: wallet.publicKey,
69
+ toPubkey: escrowPubkey,
70
+ lamports: Math.floor(amount * LAMPORTS_PER_SOL)
71
+ })
72
+ );
73
+
74
+ // Send and confirm
75
+ const signature = await sendAndConfirmTransaction(
76
+ connection,
77
+ transaction,
78
+ [wallet],
79
+ {
80
+ commitment: 'confirmed',
81
+ preflightCommitment: 'confirmed'
82
+ }
83
+ );
84
+
85
+ return signature;
86
+ } catch (error) {
87
+ throw new Error(`Stake failed: ${error.message}`);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Refund stake from escrow
93
+ */
94
+ export async function refundStake(wallet, amount) {
95
+ try {
96
+ // In a real implementation, this would call a program to refund from escrow
97
+ // For this demo, we'll simulate a refund by noting it was successful
98
+ console.log(`Refund of ${amount} SOL would be processed from escrow`);
99
+
100
+ // Mock refund transaction
101
+ // In production, this would transfer from escrow back to user
102
+ return 'MOCK_REFUND_TX_' + Date.now();
103
+ } catch (error) {
104
+ throw new Error(`Refund failed: ${error.message}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get escrow address from env or use default
110
+ */
111
+ function getEscrowAddress() {
112
+ const escrowAddress = process.env.ORACLE_ESCROW_ADDRESS;
113
+
114
+ if (!escrowAddress || escrowAddress === 'YourDevWalletPublicKeyHere') {
115
+ // Use a valid devnet address for demo (Solana's devnet faucet)
116
+ return new PublicKey('4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T');
117
+ }
118
+
119
+ try {
120
+ return new PublicKey(escrowAddress);
121
+ } catch (error) {
122
+ throw new Error(`Invalid ORACLE_ESCROW_ADDRESS in .env: ${error.message}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get wallet balance
128
+ */
129
+ export async function getBalance(publicKey) {
130
+ const balance = await connection.getBalance(publicKey);
131
+ return balance / LAMPORTS_PER_SOL;
132
+ }
package/src/x402.js ADDED
@@ -0,0 +1,245 @@
1
+ /**
2
+ * x402 Payment Integration Module
3
+ *
4
+ * Handles micropayments for premium features using HTTP 402 pattern
5
+ * and Solana for payment processing
6
+ */
7
+
8
+ import {
9
+ Transaction,
10
+ SystemProgram,
11
+ LAMPORTS_PER_SOL,
12
+ sendAndConfirmTransaction,
13
+ PublicKey
14
+ } from '@solana/web3.js';
15
+ import { getConnection } from './wallet.js';
16
+ import { fetchPremiumForecastData } from './energy-data.js';
17
+ import dotenv from 'dotenv';
18
+
19
+ dotenv.config();
20
+
21
+ const PREMIUM_PRICE = parseFloat(process.env.PREMIUM_PRICE || '0.001');
22
+ const PREMIUM_SERVICE_PUBKEY = 'DeChPrem1um111111111111111111111111111111111';
23
+
24
+ /**
25
+ * Purchase premium data access
26
+ */
27
+ export async function purchasePremiumData(wallet) {
28
+ try {
29
+ console.log(`Initiating premium purchase (${PREMIUM_PRICE} SOL)...`);
30
+
31
+ // Step 1: Request premium data (would return 402 in real implementation)
32
+ const paymentRequired = await checkPremiumAccess();
33
+
34
+ if (!paymentRequired.requiresPayment) {
35
+ // Already have access
36
+ return await fetchPremiumForecastData();
37
+ }
38
+
39
+ // Step 2: Process payment
40
+ const paymentTx = await processX402Payment(wallet, PREMIUM_PRICE);
41
+ console.log(`Payment successful: ${paymentTx}`);
42
+
43
+ // Step 3: Fetch premium data with proof of payment
44
+ const premiumData = await fetchPremiumForecastData();
45
+
46
+ return {
47
+ success: true,
48
+ transaction: paymentTx,
49
+ data: premiumData,
50
+ message: 'Premium data access granted'
51
+ };
52
+ } catch (error) {
53
+ throw new Error(`Premium purchase failed: ${error.message}`);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Check if premium access requires payment (simulates HTTP 402 check)
59
+ */
60
+ async function checkPremiumAccess() {
61
+ // In a real implementation, this would make an HTTP request
62
+ // and check for 402 Payment Required status
63
+
64
+ return {
65
+ requiresPayment: true,
66
+ price: PREMIUM_PRICE,
67
+ currency: 'SOL',
68
+ paymentAddress: PREMIUM_SERVICE_PUBKEY
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Process x402 micropayment
74
+ */
75
+ async function processX402Payment(wallet, amount) {
76
+ try {
77
+ const connection = getConnection();
78
+
79
+ // Get premium service public key
80
+ let servicePubkey;
81
+ try {
82
+ servicePubkey = new PublicKey(PREMIUM_SERVICE_PUBKEY);
83
+ } catch (error) {
84
+ // If invalid, use a test address
85
+ servicePubkey = new PublicKey('4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T');
86
+ }
87
+
88
+ // Create payment transaction
89
+ const transaction = new Transaction().add(
90
+ SystemProgram.transfer({
91
+ fromPubkey: wallet.publicKey,
92
+ toPubkey: servicePubkey,
93
+ lamports: Math.floor(amount * LAMPORTS_PER_SOL)
94
+ })
95
+ );
96
+
97
+ // Send transaction
98
+ const signature = await sendAndConfirmTransaction(
99
+ connection,
100
+ transaction,
101
+ [wallet],
102
+ {
103
+ commitment: 'confirmed',
104
+ preflightCommitment: 'confirmed'
105
+ }
106
+ );
107
+
108
+ return signature;
109
+ } catch (error) {
110
+ // If payment fails, create mock transaction for demo
111
+ if (error.message.includes('insufficient')) {
112
+ console.warn('Insufficient funds for premium, using mock payment');
113
+ return 'MOCK_PREMIUM_TX_' + Date.now();
114
+ }
115
+
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Verify x402 payment
122
+ */
123
+ export async function verifyX402Payment(signature) {
124
+ try {
125
+ if (signature.startsWith('MOCK_')) {
126
+ return {
127
+ verified: true,
128
+ mock: true,
129
+ amount: PREMIUM_PRICE
130
+ };
131
+ }
132
+
133
+ const connection = getConnection();
134
+ const tx = await connection.getTransaction(signature, {
135
+ commitment: 'confirmed'
136
+ });
137
+
138
+ if (!tx) {
139
+ return {
140
+ verified: false,
141
+ error: 'Transaction not found'
142
+ };
143
+ }
144
+
145
+ // Extract amount from transaction
146
+ const amount = tx.meta?.postBalances[0] - tx.meta?.preBalances[0];
147
+
148
+ return {
149
+ verified: true,
150
+ amount: Math.abs(amount) / LAMPORTS_PER_SOL,
151
+ timestamp: tx.blockTime
152
+ };
153
+ } catch (error) {
154
+ return {
155
+ verified: false,
156
+ error: error.message
157
+ };
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Create x402 payment channel (for recurring premium access)
163
+ */
164
+ export async function createPaymentChannel(wallet, duration = 30) {
165
+ // In a real implementation, this would create a Solana program account
166
+ // for a payment channel that allows multiple premium requests
167
+
168
+ const totalAmount = PREMIUM_PRICE * duration; // e.g., 30 days
169
+
170
+ console.log(`Creating payment channel for ${duration} days (${totalAmount} SOL)`);
171
+
172
+ try {
173
+ const connection = getConnection();
174
+ const servicePubkey = new PublicKey('4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T');
175
+
176
+ // Create channel with initial deposit
177
+ const transaction = new Transaction().add(
178
+ SystemProgram.transfer({
179
+ fromPubkey: wallet.publicKey,
180
+ toPubkey: servicePubkey,
181
+ lamports: Math.floor(totalAmount * LAMPORTS_PER_SOL)
182
+ })
183
+ );
184
+
185
+ const signature = await sendAndConfirmTransaction(
186
+ connection,
187
+ transaction,
188
+ [wallet],
189
+ {
190
+ commitment: 'confirmed'
191
+ }
192
+ );
193
+
194
+ return {
195
+ channelId: signature,
196
+ balance: totalAmount,
197
+ expiresAt: Date.now() + duration * 24 * 60 * 60 * 1000
198
+ };
199
+ } catch (error) {
200
+ throw new Error(`Failed to create payment channel: ${error.message}`);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Handle 402 Payment Required response
206
+ */
207
+ export async function handle402Response(response, wallet) {
208
+ // Parse payment requirements from response headers
209
+ const paymentInfo = {
210
+ amount: parseFloat(response.headers.get('x-payment-amount') || PREMIUM_PRICE),
211
+ currency: response.headers.get('x-payment-currency') || 'SOL',
212
+ address: response.headers.get('x-payment-address') || PREMIUM_SERVICE_PUBKEY
213
+ };
214
+
215
+ // Process payment
216
+ const paymentTx = await processX402Payment(wallet, paymentInfo.amount);
217
+
218
+ // Retry request with payment proof
219
+ const retryResponse = await fetch(response.url, {
220
+ headers: {
221
+ 'X-Payment-Proof': paymentTx,
222
+ 'X-Payment-Amount': paymentInfo.amount.toString()
223
+ }
224
+ });
225
+
226
+ return retryResponse;
227
+ }
228
+
229
+ /**
230
+ * Get premium pricing info
231
+ */
232
+ export function getPremiumPricing() {
233
+ return {
234
+ singleAccess: PREMIUM_PRICE,
235
+ daily: PREMIUM_PRICE * 10,
236
+ monthly: PREMIUM_PRICE * 30 * 0.8, // 20% discount
237
+ features: [
238
+ 'Enhanced forecast accuracy',
239
+ 'Carbon intensity data',
240
+ 'Renewable energy percentage',
241
+ 'Confidence scores',
242
+ 'Extended 48-hour forecasts'
243
+ ]
244
+ };
245
+ }