@alleyboss/micropay-solana-x402-paywall 3.1.4 → 3.2.1

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.
@@ -1,8 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var web3_js = require('@solana/web3.js');
4
+ var bs58 = require('bs58');
4
5
  var jose = require('jose');
5
6
 
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
10
+
6
11
  // src/agent/agentPayment.ts
7
12
  var DEFAULT_COMPUTE_UNITS = 2e5;
8
13
  var DEFAULT_MICRO_LAMPORTS = 1e3;
@@ -51,8 +56,6 @@ async function buildVersionedTransaction(config) {
51
56
  lastValidBlockHeight
52
57
  };
53
58
  }
54
-
55
- // src/agent/agentPayment.ts
56
59
  var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
57
60
  function isValidWalletAddress(address) {
58
61
  if (!address || typeof address !== "string") return false;
@@ -153,15 +156,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
153
156
  };
154
157
  }
155
158
  function keypairFromBase58(base58Secret) {
156
- const bytes = Buffer.from(base58Secret, "base64");
157
- if (bytes.length !== 64) {
159
+ try {
160
+ const bytes = bs58__default.default.decode(base58Secret);
161
+ if (bytes.length !== 64) {
162
+ throw new Error("Invalid secret key length. Expected 64 bytes.");
163
+ }
164
+ return web3_js.Keypair.fromSecretKey(bytes);
165
+ } catch (error) {
158
166
  const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
159
167
  if (parts.length === 64) {
160
168
  return web3_js.Keypair.fromSecretKey(Uint8Array.from(parts));
161
169
  }
162
170
  throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
163
171
  }
164
- return web3_js.Keypair.fromSecretKey(bytes);
165
172
  }
166
173
  function generateAgentKeypair() {
167
174
  const keypair = web3_js.Keypair.generate();
@@ -1,4 +1,5 @@
1
1
  import { PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
2
3
  import { SignJWT, jwtVerify } from 'jose';
3
4
 
4
5
  // src/agent/agentPayment.ts
@@ -49,8 +50,6 @@ async function buildVersionedTransaction(config) {
49
50
  lastValidBlockHeight
50
51
  };
51
52
  }
52
-
53
- // src/agent/agentPayment.ts
54
53
  var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
55
54
  function isValidWalletAddress(address) {
56
55
  if (!address || typeof address !== "string") return false;
@@ -151,15 +150,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
151
150
  };
152
151
  }
153
152
  function keypairFromBase58(base58Secret) {
154
- const bytes = Buffer.from(base58Secret, "base64");
155
- if (bytes.length !== 64) {
153
+ try {
154
+ const bytes = bs58.decode(base58Secret);
155
+ if (bytes.length !== 64) {
156
+ throw new Error("Invalid secret key length. Expected 64 bytes.");
157
+ }
158
+ return Keypair.fromSecretKey(bytes);
159
+ } catch (error) {
156
160
  const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
157
161
  if (parts.length === 64) {
158
162
  return Keypair.fromSecretKey(Uint8Array.from(parts));
159
163
  }
160
164
  throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
161
165
  }
162
- return Keypair.fromSecretKey(bytes);
163
166
  }
164
167
  function generateAgentKeypair() {
165
168
  const keypair = Keypair.generate();
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var http = require('@x402/core/http');
4
+ var web3_js = require('@solana/web3.js');
5
+ var react = require('react');
4
6
 
5
7
  // src/client/types.ts
6
8
  var TOKEN_MINTS = {
@@ -111,8 +113,137 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
111
113
  const token = http.encodePaymentSignatureHeader(payload);
112
114
  return `x402 ${token}`;
113
115
  }
116
+ async function sendSolanaPayment({
117
+ connection,
118
+ wallet,
119
+ recipientAddress,
120
+ amount,
121
+ // memo, // TODO: Add memo support to transaction
122
+ commitment = "confirmed"
123
+ }) {
124
+ if (!wallet.publicKey) {
125
+ throw new Error("Wallet not connected");
126
+ }
127
+ if (amount <= 0n) {
128
+ throw new Error("Amount must be greater than 0");
129
+ }
130
+ try {
131
+ const recipientPubkey = new web3_js.PublicKey(recipientAddress);
132
+ const transaction = new web3_js.Transaction().add(
133
+ web3_js.SystemProgram.transfer({
134
+ fromPubkey: wallet.publicKey,
135
+ toPubkey: recipientPubkey,
136
+ lamports: amount
137
+ })
138
+ );
139
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
140
+ transaction.recentBlockhash = blockhash;
141
+ transaction.feePayer = wallet.publicKey;
142
+ const signature = await wallet.sendTransaction(transaction, connection);
143
+ await connection.confirmTransaction({
144
+ signature,
145
+ blockhash,
146
+ lastValidBlockHeight
147
+ }, commitment);
148
+ return {
149
+ signature,
150
+ amountSol: Number(amount) / web3_js.LAMPORTS_PER_SOL
151
+ };
152
+ } catch (error) {
153
+ console.error("Payment failed:", error);
154
+ throw new Error(error.message || "Payment failed");
155
+ }
156
+ }
157
+ function usePaywallResource({
158
+ url,
159
+ connection,
160
+ wallet
161
+ }) {
162
+ const [data, setData] = react.useState(null);
163
+ const [isLocked, setIsLocked] = react.useState(true);
164
+ const [isLoading, setIsLoading] = react.useState(true);
165
+ const [error, setError] = react.useState(null);
166
+ const [paymentHeader, setPaymentHeader] = react.useState(null);
167
+ const [price, setPrice] = react.useState();
168
+ const [recipient, setRecipient] = react.useState();
169
+ const fetchData = react.useCallback(async (authHeader) => {
170
+ setIsLoading(true);
171
+ setError(null);
172
+ try {
173
+ const headers = {
174
+ "Content-Type": "application/json"
175
+ };
176
+ if (authHeader) {
177
+ headers["Authorization"] = authHeader;
178
+ }
179
+ const res = await fetch(url, { headers });
180
+ if (res.status === 402) {
181
+ const wwwAuth = res.headers.get("WWW-Authenticate");
182
+ if (wwwAuth) {
183
+ setPaymentHeader(wwwAuth);
184
+ try {
185
+ const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
186
+ if (match) {
187
+ setPrice(BigInt(match[1]));
188
+ setRecipient(match[2]);
189
+ }
190
+ } catch (e) {
191
+ console.warn("Failed to parse 402 details from header", e);
192
+ }
193
+ }
194
+ setIsLocked(true);
195
+ setData(null);
196
+ } else if (res.ok) {
197
+ const json = await res.json();
198
+ setData(json);
199
+ setIsLocked(false);
200
+ } else {
201
+ throw new Error(`Request failed with status ${res.status}`);
202
+ }
203
+ } catch (err) {
204
+ setError(err.message || "Failed to fetch resource");
205
+ } finally {
206
+ setIsLoading(false);
207
+ }
208
+ }, [url]);
209
+ react.useEffect(() => {
210
+ fetchData();
211
+ }, [fetchData]);
212
+ const unlock = react.useCallback(async () => {
213
+ if (!paymentHeader || !price || !recipient) {
214
+ setError("Missing payment requirements");
215
+ return;
216
+ }
217
+ setIsLoading(true);
218
+ try {
219
+ const { signature } = await sendSolanaPayment({
220
+ connection,
221
+ wallet,
222
+ recipientAddress: recipient,
223
+ amount: price
224
+ });
225
+ const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
226
+ await fetchData(authHeader);
227
+ } catch (err) {
228
+ console.error("Unlock failed", err);
229
+ setError(err.message || "Payment/Unlock failed");
230
+ setIsLoading(false);
231
+ }
232
+ }, [connection, wallet, paymentHeader, price, recipient, fetchData]);
233
+ return {
234
+ data,
235
+ isLocked,
236
+ isLoading,
237
+ price,
238
+ recipient,
239
+ error,
240
+ unlock
241
+ };
242
+ }
114
243
 
115
244
  exports.buildSolanaPayUrl = buildSolanaPayUrl;
116
245
  exports.createPaymentFlow = createPaymentFlow;
117
246
  exports.createPaymentReference = createPaymentReference;
118
247
  exports.createX402AuthorizationHeader = createX402AuthorizationHeader;
248
+ exports.sendSolanaPayment = sendSolanaPayment;
249
+ exports.usePaywallResource = usePaywallResource;
@@ -1,3 +1,5 @@
1
+ import { PublicKey, Transaction, Connection, TransactionSignature } from '@solana/web3.js';
2
+
1
3
  /** SPL Token asset specification */
2
4
  interface SPLTokenAsset {
3
5
  /** Token mint address */
@@ -126,4 +128,95 @@ declare function createPaymentReference(): string;
126
128
  */
127
129
  declare function createX402AuthorizationHeader(signature: string, paymentRequiredHeader: string): string;
128
130
 
129
- export { type PaymentFlowConfig, type SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader };
131
+ /**
132
+ * Minimal wallet adapter interface compatible with @solana/wallet-adapter-base
133
+ */
134
+ interface WalletAdapterInterface {
135
+ publicKey: PublicKey | null;
136
+ signTransaction?: (transaction: Transaction) => Promise<Transaction>;
137
+ sendTransaction: (transaction: Transaction, connection: Connection, options?: any) => Promise<TransactionSignature>;
138
+ }
139
+ interface SendPaymentParams {
140
+ /** Solana Connection object */
141
+ connection: Connection;
142
+ /** Connected wallet adapter */
143
+ wallet: WalletAdapterInterface;
144
+ /** Recipient wallet address */
145
+ recipientAddress: string;
146
+ /** Amount in lamports */
147
+ amount: bigint;
148
+ /** Optional memo for the transaction */
149
+ memo?: string;
150
+ /** Commitment level (default: 'confirmed') */
151
+ commitment?: 'processed' | 'confirmed' | 'finalized';
152
+ }
153
+ interface PaymentResult {
154
+ signature: string;
155
+ amountSol: number;
156
+ }
157
+ /**
158
+ * Send a SOL payment from the client-side wallet
159
+ *
160
+ * Handles transaction creation, recent blockhash, and sending.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const result = await sendSolanaPayment({
165
+ * connection,
166
+ * wallet,
167
+ * recipientAddress: '...',
168
+ * amount: 10_000_000n // 0.01 SOL
169
+ * });
170
+ * ```
171
+ */
172
+ declare function sendSolanaPayment({ connection, wallet, recipientAddress, amount, commitment }: SendPaymentParams): Promise<PaymentResult>;
173
+
174
+ interface PaywallConfig {
175
+ /** URL to fetch protected content from */
176
+ url: string;
177
+ /** Solana Connection */
178
+ connection: Connection;
179
+ /** Wallet Adapter */
180
+ wallet: WalletAdapterInterface;
181
+ }
182
+ interface PaywallState<T> {
183
+ /** Content data if unlocked */
184
+ data: T | null;
185
+ /** Whether content is currently locked by 402 */
186
+ isLocked: boolean;
187
+ /** Whether an operation is in progress (initial load or unlocking) */
188
+ isLoading: boolean;
189
+ /** Payment amount required (in lamports) */
190
+ price?: bigint;
191
+ /** Recipient address */
192
+ recipient?: string;
193
+ /** Error message */
194
+ error: string | null;
195
+ /** Function to trigger unlock flow */
196
+ unlock: () => Promise<void>;
197
+ }
198
+ /**
199
+ * React Hook to manage x402 Payment Loop
200
+ *
201
+ * Handles:
202
+ * 1. Initial fetch
203
+ * 2. 402 Payment Required detection
204
+ * 3. Payment execution (via wallet)
205
+ * 4. Auth header creation
206
+ * 5. Re-fetch with proof
207
+ *
208
+ * @example
209
+ * ```tsx
210
+ * const { data, isLocked, unlock, price } = usePaywallResource<Article>({
211
+ * url: `/api/articles/${slug}`,
212
+ * connection,
213
+ * wallet
214
+ * });
215
+ *
216
+ * if (isLocked) return <Button onClick={unlock}>Pay {price} Lamports</Button>;
217
+ * return <Article data={data} />;
218
+ * ```
219
+ */
220
+ declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
221
+
222
+ export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
@@ -1,3 +1,5 @@
1
+ import { PublicKey, Transaction, Connection, TransactionSignature } from '@solana/web3.js';
2
+
1
3
  /** SPL Token asset specification */
2
4
  interface SPLTokenAsset {
3
5
  /** Token mint address */
@@ -126,4 +128,95 @@ declare function createPaymentReference(): string;
126
128
  */
127
129
  declare function createX402AuthorizationHeader(signature: string, paymentRequiredHeader: string): string;
128
130
 
129
- export { type PaymentFlowConfig, type SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader };
131
+ /**
132
+ * Minimal wallet adapter interface compatible with @solana/wallet-adapter-base
133
+ */
134
+ interface WalletAdapterInterface {
135
+ publicKey: PublicKey | null;
136
+ signTransaction?: (transaction: Transaction) => Promise<Transaction>;
137
+ sendTransaction: (transaction: Transaction, connection: Connection, options?: any) => Promise<TransactionSignature>;
138
+ }
139
+ interface SendPaymentParams {
140
+ /** Solana Connection object */
141
+ connection: Connection;
142
+ /** Connected wallet adapter */
143
+ wallet: WalletAdapterInterface;
144
+ /** Recipient wallet address */
145
+ recipientAddress: string;
146
+ /** Amount in lamports */
147
+ amount: bigint;
148
+ /** Optional memo for the transaction */
149
+ memo?: string;
150
+ /** Commitment level (default: 'confirmed') */
151
+ commitment?: 'processed' | 'confirmed' | 'finalized';
152
+ }
153
+ interface PaymentResult {
154
+ signature: string;
155
+ amountSol: number;
156
+ }
157
+ /**
158
+ * Send a SOL payment from the client-side wallet
159
+ *
160
+ * Handles transaction creation, recent blockhash, and sending.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const result = await sendSolanaPayment({
165
+ * connection,
166
+ * wallet,
167
+ * recipientAddress: '...',
168
+ * amount: 10_000_000n // 0.01 SOL
169
+ * });
170
+ * ```
171
+ */
172
+ declare function sendSolanaPayment({ connection, wallet, recipientAddress, amount, commitment }: SendPaymentParams): Promise<PaymentResult>;
173
+
174
+ interface PaywallConfig {
175
+ /** URL to fetch protected content from */
176
+ url: string;
177
+ /** Solana Connection */
178
+ connection: Connection;
179
+ /** Wallet Adapter */
180
+ wallet: WalletAdapterInterface;
181
+ }
182
+ interface PaywallState<T> {
183
+ /** Content data if unlocked */
184
+ data: T | null;
185
+ /** Whether content is currently locked by 402 */
186
+ isLocked: boolean;
187
+ /** Whether an operation is in progress (initial load or unlocking) */
188
+ isLoading: boolean;
189
+ /** Payment amount required (in lamports) */
190
+ price?: bigint;
191
+ /** Recipient address */
192
+ recipient?: string;
193
+ /** Error message */
194
+ error: string | null;
195
+ /** Function to trigger unlock flow */
196
+ unlock: () => Promise<void>;
197
+ }
198
+ /**
199
+ * React Hook to manage x402 Payment Loop
200
+ *
201
+ * Handles:
202
+ * 1. Initial fetch
203
+ * 2. 402 Payment Required detection
204
+ * 3. Payment execution (via wallet)
205
+ * 4. Auth header creation
206
+ * 5. Re-fetch with proof
207
+ *
208
+ * @example
209
+ * ```tsx
210
+ * const { data, isLocked, unlock, price } = usePaywallResource<Article>({
211
+ * url: `/api/articles/${slug}`,
212
+ * connection,
213
+ * wallet
214
+ * });
215
+ *
216
+ * if (isLocked) return <Button onClick={unlock}>Pay {price} Lamports</Button>;
217
+ * return <Article data={data} />;
218
+ * ```
219
+ */
220
+ declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
221
+
222
+ export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
@@ -1,4 +1,6 @@
1
1
  import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
2
+ import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL } from '@solana/web3.js';
3
+ import { useState, useCallback, useEffect } from 'react';
2
4
 
3
5
  // src/client/types.ts
4
6
  var TOKEN_MINTS = {
@@ -109,5 +111,132 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
109
111
  const token = encodePaymentSignatureHeader(payload);
110
112
  return `x402 ${token}`;
111
113
  }
114
+ async function sendSolanaPayment({
115
+ connection,
116
+ wallet,
117
+ recipientAddress,
118
+ amount,
119
+ // memo, // TODO: Add memo support to transaction
120
+ commitment = "confirmed"
121
+ }) {
122
+ if (!wallet.publicKey) {
123
+ throw new Error("Wallet not connected");
124
+ }
125
+ if (amount <= 0n) {
126
+ throw new Error("Amount must be greater than 0");
127
+ }
128
+ try {
129
+ const recipientPubkey = new PublicKey(recipientAddress);
130
+ const transaction = new Transaction().add(
131
+ SystemProgram.transfer({
132
+ fromPubkey: wallet.publicKey,
133
+ toPubkey: recipientPubkey,
134
+ lamports: amount
135
+ })
136
+ );
137
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
138
+ transaction.recentBlockhash = blockhash;
139
+ transaction.feePayer = wallet.publicKey;
140
+ const signature = await wallet.sendTransaction(transaction, connection);
141
+ await connection.confirmTransaction({
142
+ signature,
143
+ blockhash,
144
+ lastValidBlockHeight
145
+ }, commitment);
146
+ return {
147
+ signature,
148
+ amountSol: Number(amount) / LAMPORTS_PER_SOL
149
+ };
150
+ } catch (error) {
151
+ console.error("Payment failed:", error);
152
+ throw new Error(error.message || "Payment failed");
153
+ }
154
+ }
155
+ function usePaywallResource({
156
+ url,
157
+ connection,
158
+ wallet
159
+ }) {
160
+ const [data, setData] = useState(null);
161
+ const [isLocked, setIsLocked] = useState(true);
162
+ const [isLoading, setIsLoading] = useState(true);
163
+ const [error, setError] = useState(null);
164
+ const [paymentHeader, setPaymentHeader] = useState(null);
165
+ const [price, setPrice] = useState();
166
+ const [recipient, setRecipient] = useState();
167
+ const fetchData = useCallback(async (authHeader) => {
168
+ setIsLoading(true);
169
+ setError(null);
170
+ try {
171
+ const headers = {
172
+ "Content-Type": "application/json"
173
+ };
174
+ if (authHeader) {
175
+ headers["Authorization"] = authHeader;
176
+ }
177
+ const res = await fetch(url, { headers });
178
+ if (res.status === 402) {
179
+ const wwwAuth = res.headers.get("WWW-Authenticate");
180
+ if (wwwAuth) {
181
+ setPaymentHeader(wwwAuth);
182
+ try {
183
+ const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
184
+ if (match) {
185
+ setPrice(BigInt(match[1]));
186
+ setRecipient(match[2]);
187
+ }
188
+ } catch (e) {
189
+ console.warn("Failed to parse 402 details from header", e);
190
+ }
191
+ }
192
+ setIsLocked(true);
193
+ setData(null);
194
+ } else if (res.ok) {
195
+ const json = await res.json();
196
+ setData(json);
197
+ setIsLocked(false);
198
+ } else {
199
+ throw new Error(`Request failed with status ${res.status}`);
200
+ }
201
+ } catch (err) {
202
+ setError(err.message || "Failed to fetch resource");
203
+ } finally {
204
+ setIsLoading(false);
205
+ }
206
+ }, [url]);
207
+ useEffect(() => {
208
+ fetchData();
209
+ }, [fetchData]);
210
+ const unlock = useCallback(async () => {
211
+ if (!paymentHeader || !price || !recipient) {
212
+ setError("Missing payment requirements");
213
+ return;
214
+ }
215
+ setIsLoading(true);
216
+ try {
217
+ const { signature } = await sendSolanaPayment({
218
+ connection,
219
+ wallet,
220
+ recipientAddress: recipient,
221
+ amount: price
222
+ });
223
+ const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
224
+ await fetchData(authHeader);
225
+ } catch (err) {
226
+ console.error("Unlock failed", err);
227
+ setError(err.message || "Payment/Unlock failed");
228
+ setIsLoading(false);
229
+ }
230
+ }, [connection, wallet, paymentHeader, price, recipient, fetchData]);
231
+ return {
232
+ data,
233
+ isLocked,
234
+ isLoading,
235
+ price,
236
+ recipient,
237
+ error,
238
+ unlock
239
+ };
240
+ }
112
241
 
113
- export { buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader };
242
+ export { buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
package/dist/index.cjs CHANGED
@@ -6,8 +6,14 @@ var client = require('@x402/core/client');
6
6
  var svm = require('@x402/svm');
7
7
  var http = require('@x402/core/http');
8
8
  var web3_js = require('@solana/web3.js');
9
+ var react = require('react');
10
+ var bs58 = require('bs58');
9
11
  var jose = require('jose');
10
12
 
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
+
15
+ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
16
+
11
17
  // src/index.ts
12
18
 
13
19
  // src/client/types.ts
@@ -119,6 +125,133 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
119
125
  const token = http.encodePaymentSignatureHeader(payload);
120
126
  return `x402 ${token}`;
121
127
  }
128
+ async function sendSolanaPayment({
129
+ connection,
130
+ wallet,
131
+ recipientAddress,
132
+ amount,
133
+ // memo, // TODO: Add memo support to transaction
134
+ commitment = "confirmed"
135
+ }) {
136
+ if (!wallet.publicKey) {
137
+ throw new Error("Wallet not connected");
138
+ }
139
+ if (amount <= 0n) {
140
+ throw new Error("Amount must be greater than 0");
141
+ }
142
+ try {
143
+ const recipientPubkey = new web3_js.PublicKey(recipientAddress);
144
+ const transaction = new web3_js.Transaction().add(
145
+ web3_js.SystemProgram.transfer({
146
+ fromPubkey: wallet.publicKey,
147
+ toPubkey: recipientPubkey,
148
+ lamports: amount
149
+ })
150
+ );
151
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
152
+ transaction.recentBlockhash = blockhash;
153
+ transaction.feePayer = wallet.publicKey;
154
+ const signature = await wallet.sendTransaction(transaction, connection);
155
+ await connection.confirmTransaction({
156
+ signature,
157
+ blockhash,
158
+ lastValidBlockHeight
159
+ }, commitment);
160
+ return {
161
+ signature,
162
+ amountSol: Number(amount) / web3_js.LAMPORTS_PER_SOL
163
+ };
164
+ } catch (error) {
165
+ console.error("Payment failed:", error);
166
+ throw new Error(error.message || "Payment failed");
167
+ }
168
+ }
169
+ function usePaywallResource({
170
+ url,
171
+ connection,
172
+ wallet
173
+ }) {
174
+ const [data, setData] = react.useState(null);
175
+ const [isLocked, setIsLocked] = react.useState(true);
176
+ const [isLoading, setIsLoading] = react.useState(true);
177
+ const [error, setError] = react.useState(null);
178
+ const [paymentHeader, setPaymentHeader] = react.useState(null);
179
+ const [price, setPrice] = react.useState();
180
+ const [recipient, setRecipient] = react.useState();
181
+ const fetchData = react.useCallback(async (authHeader) => {
182
+ setIsLoading(true);
183
+ setError(null);
184
+ try {
185
+ const headers = {
186
+ "Content-Type": "application/json"
187
+ };
188
+ if (authHeader) {
189
+ headers["Authorization"] = authHeader;
190
+ }
191
+ const res = await fetch(url, { headers });
192
+ if (res.status === 402) {
193
+ const wwwAuth = res.headers.get("WWW-Authenticate");
194
+ if (wwwAuth) {
195
+ setPaymentHeader(wwwAuth);
196
+ try {
197
+ const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
198
+ if (match) {
199
+ setPrice(BigInt(match[1]));
200
+ setRecipient(match[2]);
201
+ }
202
+ } catch (e) {
203
+ console.warn("Failed to parse 402 details from header", e);
204
+ }
205
+ }
206
+ setIsLocked(true);
207
+ setData(null);
208
+ } else if (res.ok) {
209
+ const json = await res.json();
210
+ setData(json);
211
+ setIsLocked(false);
212
+ } else {
213
+ throw new Error(`Request failed with status ${res.status}`);
214
+ }
215
+ } catch (err) {
216
+ setError(err.message || "Failed to fetch resource");
217
+ } finally {
218
+ setIsLoading(false);
219
+ }
220
+ }, [url]);
221
+ react.useEffect(() => {
222
+ fetchData();
223
+ }, [fetchData]);
224
+ const unlock = react.useCallback(async () => {
225
+ if (!paymentHeader || !price || !recipient) {
226
+ setError("Missing payment requirements");
227
+ return;
228
+ }
229
+ setIsLoading(true);
230
+ try {
231
+ const { signature } = await sendSolanaPayment({
232
+ connection,
233
+ wallet,
234
+ recipientAddress: recipient,
235
+ amount: price
236
+ });
237
+ const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
238
+ await fetchData(authHeader);
239
+ } catch (err) {
240
+ console.error("Unlock failed", err);
241
+ setError(err.message || "Payment/Unlock failed");
242
+ setIsLoading(false);
243
+ }
244
+ }, [connection, wallet, paymentHeader, price, recipient, fetchData]);
245
+ return {
246
+ data,
247
+ isLocked,
248
+ isLoading,
249
+ price,
250
+ recipient,
251
+ error,
252
+ unlock
253
+ };
254
+ }
122
255
  var DEFAULT_COMPUTE_UNITS = 2e5;
123
256
  var DEFAULT_MICRO_LAMPORTS = 1e3;
124
257
  function createPriorityFeeInstructions(config2 = {}) {
@@ -166,8 +299,6 @@ async function buildVersionedTransaction(config2) {
166
299
  lastValidBlockHeight
167
300
  };
168
301
  }
169
-
170
- // src/agent/agentPayment.ts
171
302
  var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
172
303
  function isValidWalletAddress(address) {
173
304
  if (!address || typeof address !== "string") return false;
@@ -268,15 +399,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
268
399
  };
269
400
  }
270
401
  function keypairFromBase58(base58Secret) {
271
- const bytes = Buffer.from(base58Secret, "base64");
272
- if (bytes.length !== 64) {
402
+ try {
403
+ const bytes = bs58__default.default.decode(base58Secret);
404
+ if (bytes.length !== 64) {
405
+ throw new Error("Invalid secret key length. Expected 64 bytes.");
406
+ }
407
+ return web3_js.Keypair.fromSecretKey(bytes);
408
+ } catch (error) {
273
409
  const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
274
410
  if (parts.length === 64) {
275
411
  return web3_js.Keypair.fromSecretKey(Uint8Array.from(parts));
276
412
  }
277
413
  throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
278
414
  }
279
- return web3_js.Keypair.fromSecretKey(bytes);
280
415
  }
281
416
  function generateAgentKeypair() {
282
417
  const keypair = web3_js.Keypair.generate();
@@ -614,8 +749,10 @@ exports.hasAgentSufficientBalance = hasAgentSufficientBalance;
614
749
  exports.keypairFromBase58 = keypairFromBase58;
615
750
  exports.lamportsToSol = lamportsToSol;
616
751
  exports.lamportsToUsd = lamportsToUsd;
752
+ exports.sendSolanaPayment = sendSolanaPayment;
617
753
  exports.usdToLamports = usdToLamports;
618
754
  exports.useCredit = useCredit;
755
+ exports.usePaywallResource = usePaywallResource;
619
756
  exports.validateCreditSession = validateCreditSession;
620
757
  Object.keys(core).forEach(function (k) {
621
758
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ export * from '@x402/core';
2
2
  export * from '@x402/core/types';
3
3
  export * from '@x402/core/client';
4
4
  export * from '@x402/svm';
5
- export { PaymentFlowConfig, SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader } from './client/index.cjs';
5
+ export { PaymentFlowConfig, PaymentResult, PaywallConfig, PaywallState, SendPaymentParams, SolanaPayUrlParams, WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource } from './client/index.cjs';
6
6
  export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSessionData, CreditValidation, ExecuteAgentPaymentParams, UseCreditResult, addCredits, createCreditSession, executeAgentPayment, generateAgentKeypair, getAgentBalance, getRemainingCredits, hasAgentSufficientBalance, keypairFromBase58, useCredit, validateCreditSession } from './agent/index.cjs';
7
7
  export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.cjs';
8
8
  import '@solana/web3.js';
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from '@x402/core';
2
2
  export * from '@x402/core/types';
3
3
  export * from '@x402/core/client';
4
4
  export * from '@x402/svm';
5
- export { PaymentFlowConfig, SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader } from './client/index.js';
5
+ export { PaymentFlowConfig, PaymentResult, PaywallConfig, PaywallState, SendPaymentParams, SolanaPayUrlParams, WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource } from './client/index.js';
6
6
  export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSessionData, CreditValidation, ExecuteAgentPaymentParams, UseCreditResult, addCredits, createCreditSession, executeAgentPayment, generateAgentKeypair, getAgentBalance, getRemainingCredits, hasAgentSufficientBalance, keypairFromBase58, useCredit, validateCreditSession } from './agent/index.js';
7
7
  export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.js';
8
8
  import '@solana/web3.js';
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ export * from '@x402/core/types';
3
3
  export * from '@x402/core/client';
4
4
  export * from '@x402/svm';
5
5
  import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
6
- import { PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
6
+ import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
7
+ import { useState, useCallback, useEffect } from 'react';
8
+ import bs58 from 'bs58';
7
9
  import { SignJWT, jwtVerify } from 'jose';
8
10
 
9
11
  // src/index.ts
@@ -117,6 +119,133 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
117
119
  const token = encodePaymentSignatureHeader(payload);
118
120
  return `x402 ${token}`;
119
121
  }
122
+ async function sendSolanaPayment({
123
+ connection,
124
+ wallet,
125
+ recipientAddress,
126
+ amount,
127
+ // memo, // TODO: Add memo support to transaction
128
+ commitment = "confirmed"
129
+ }) {
130
+ if (!wallet.publicKey) {
131
+ throw new Error("Wallet not connected");
132
+ }
133
+ if (amount <= 0n) {
134
+ throw new Error("Amount must be greater than 0");
135
+ }
136
+ try {
137
+ const recipientPubkey = new PublicKey(recipientAddress);
138
+ const transaction = new Transaction().add(
139
+ SystemProgram.transfer({
140
+ fromPubkey: wallet.publicKey,
141
+ toPubkey: recipientPubkey,
142
+ lamports: amount
143
+ })
144
+ );
145
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
146
+ transaction.recentBlockhash = blockhash;
147
+ transaction.feePayer = wallet.publicKey;
148
+ const signature = await wallet.sendTransaction(transaction, connection);
149
+ await connection.confirmTransaction({
150
+ signature,
151
+ blockhash,
152
+ lastValidBlockHeight
153
+ }, commitment);
154
+ return {
155
+ signature,
156
+ amountSol: Number(amount) / LAMPORTS_PER_SOL
157
+ };
158
+ } catch (error) {
159
+ console.error("Payment failed:", error);
160
+ throw new Error(error.message || "Payment failed");
161
+ }
162
+ }
163
+ function usePaywallResource({
164
+ url,
165
+ connection,
166
+ wallet
167
+ }) {
168
+ const [data, setData] = useState(null);
169
+ const [isLocked, setIsLocked] = useState(true);
170
+ const [isLoading, setIsLoading] = useState(true);
171
+ const [error, setError] = useState(null);
172
+ const [paymentHeader, setPaymentHeader] = useState(null);
173
+ const [price, setPrice] = useState();
174
+ const [recipient, setRecipient] = useState();
175
+ const fetchData = useCallback(async (authHeader) => {
176
+ setIsLoading(true);
177
+ setError(null);
178
+ try {
179
+ const headers = {
180
+ "Content-Type": "application/json"
181
+ };
182
+ if (authHeader) {
183
+ headers["Authorization"] = authHeader;
184
+ }
185
+ const res = await fetch(url, { headers });
186
+ if (res.status === 402) {
187
+ const wwwAuth = res.headers.get("WWW-Authenticate");
188
+ if (wwwAuth) {
189
+ setPaymentHeader(wwwAuth);
190
+ try {
191
+ const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
192
+ if (match) {
193
+ setPrice(BigInt(match[1]));
194
+ setRecipient(match[2]);
195
+ }
196
+ } catch (e) {
197
+ console.warn("Failed to parse 402 details from header", e);
198
+ }
199
+ }
200
+ setIsLocked(true);
201
+ setData(null);
202
+ } else if (res.ok) {
203
+ const json = await res.json();
204
+ setData(json);
205
+ setIsLocked(false);
206
+ } else {
207
+ throw new Error(`Request failed with status ${res.status}`);
208
+ }
209
+ } catch (err) {
210
+ setError(err.message || "Failed to fetch resource");
211
+ } finally {
212
+ setIsLoading(false);
213
+ }
214
+ }, [url]);
215
+ useEffect(() => {
216
+ fetchData();
217
+ }, [fetchData]);
218
+ const unlock = useCallback(async () => {
219
+ if (!paymentHeader || !price || !recipient) {
220
+ setError("Missing payment requirements");
221
+ return;
222
+ }
223
+ setIsLoading(true);
224
+ try {
225
+ const { signature } = await sendSolanaPayment({
226
+ connection,
227
+ wallet,
228
+ recipientAddress: recipient,
229
+ amount: price
230
+ });
231
+ const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
232
+ await fetchData(authHeader);
233
+ } catch (err) {
234
+ console.error("Unlock failed", err);
235
+ setError(err.message || "Payment/Unlock failed");
236
+ setIsLoading(false);
237
+ }
238
+ }, [connection, wallet, paymentHeader, price, recipient, fetchData]);
239
+ return {
240
+ data,
241
+ isLocked,
242
+ isLoading,
243
+ price,
244
+ recipient,
245
+ error,
246
+ unlock
247
+ };
248
+ }
120
249
  var DEFAULT_COMPUTE_UNITS = 2e5;
121
250
  var DEFAULT_MICRO_LAMPORTS = 1e3;
122
251
  function createPriorityFeeInstructions(config2 = {}) {
@@ -164,8 +293,6 @@ async function buildVersionedTransaction(config2) {
164
293
  lastValidBlockHeight
165
294
  };
166
295
  }
167
-
168
- // src/agent/agentPayment.ts
169
296
  var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
170
297
  function isValidWalletAddress(address) {
171
298
  if (!address || typeof address !== "string") return false;
@@ -266,15 +393,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
266
393
  };
267
394
  }
268
395
  function keypairFromBase58(base58Secret) {
269
- const bytes = Buffer.from(base58Secret, "base64");
270
- if (bytes.length !== 64) {
396
+ try {
397
+ const bytes = bs58.decode(base58Secret);
398
+ if (bytes.length !== 64) {
399
+ throw new Error("Invalid secret key length. Expected 64 bytes.");
400
+ }
401
+ return Keypair.fromSecretKey(bytes);
402
+ } catch (error) {
271
403
  const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
272
404
  if (parts.length === 64) {
273
405
  return Keypair.fromSecretKey(Uint8Array.from(parts));
274
406
  }
275
407
  throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
276
408
  }
277
- return Keypair.fromSecretKey(bytes);
278
409
  }
279
410
  function generateAgentKeypair() {
280
411
  const keypair = Keypair.generate();
@@ -592,4 +723,4 @@ function getProviders() {
592
723
  return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
593
724
  }
594
725
 
595
- export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, usdToLamports, useCredit, validateCreditSession };
726
+ export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, usePaywallResource, validateCreditSession };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alleyboss/micropay-solana-x402-paywall",
3
- "version": "3.1.4",
3
+ "version": "3.2.1",
4
4
  "description": "Production-ready Solana micropayments library wrapper for official x402 SDK",
5
5
  "author": "AlleyBoss",
6
6
  "license": "MIT",
@@ -108,20 +108,26 @@
108
108
  "prepublishOnly": "npm run test && npm run build"
109
109
  },
110
110
  "dependencies": {
111
+ "@types/bs58": "^4.0.4",
111
112
  "@x402/core": "^2.1.0",
112
113
  "@x402/next": "^2.1.0",
113
114
  "@x402/svm": "^2.1.0",
115
+ "bs58": "^6.0.0",
114
116
  "jose": "^6.1.3"
115
117
  },
116
118
  "peerDependencies": {
117
- "@solana/web3.js": "^1.90.0"
119
+ "@solana/web3.js": "^1.90.0",
120
+ "react": ">=16.8.0",
121
+ "react-dom": ">=16.8.0"
118
122
  },
119
123
  "devDependencies": {
120
124
  "@solana/web3.js": "^1.98.4",
121
125
  "@types/express": "^5.0.6",
122
126
  "@types/node": "^20",
127
+ "@types/react": "^19.2.7",
123
128
  "@types/uuid": "^10.0.0",
124
129
  "express": "^5.2.1",
130
+ "react": "^19.2.3",
125
131
  "tsup": "^8.0.0",
126
132
  "typescript": "^5",
127
133
  "vitest": "^3.0.0"